react-msaview 6.4.2 → 7.0.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 (212) hide show
  1. package/README.md +6 -5
  2. package/bundle/index.js +100 -101
  3. package/bundle/index.js.map +4 -4
  4. package/dist/components/Loading.js +2 -2
  5. package/dist/components/Loading.js.map +1 -1
  6. package/dist/components/MSAViewer.d.ts +10 -2
  7. package/dist/components/MSAViewer.js +90 -2
  8. package/dist/components/MSAViewer.js.map +1 -1
  9. package/dist/components/ResizeHandles.d.ts +3 -3
  10. package/dist/components/ResizeHandles.js +28 -21
  11. package/dist/components/ResizeHandles.js.map +1 -1
  12. package/dist/components/dialogs/{InterProScanDialog.d.ts → AnnotationFileDialog.d.ts} +2 -2
  13. package/dist/components/dialogs/AnnotationFileDialog.js +92 -0
  14. package/dist/components/dialogs/AnnotationFileDialog.js.map +1 -0
  15. package/dist/components/dialogs/FeatureDialog.js +13 -9
  16. package/dist/components/dialogs/FeatureDialog.js.map +1 -1
  17. package/dist/components/header/Header.js +4 -0
  18. package/dist/components/header/Header.js.map +1 -1
  19. package/dist/components/header/HeaderStatusArea.js +0 -2
  20. package/dist/components/header/HeaderStatusArea.js.map +1 -1
  21. package/dist/components/header/LoadWarnings.d.ts +6 -0
  22. package/dist/components/header/LoadWarnings.js +36 -0
  23. package/dist/components/header/LoadWarnings.js.map +1 -0
  24. package/dist/components/header/MultiAlignmentSelector.js +0 -0
  25. package/dist/components/header/MultiAlignmentSelector.js.map +1 -1
  26. package/dist/components/header/TreeSettingsMenu.js +9 -1
  27. package/dist/components/header/TreeSettingsMenu.js.map +1 -1
  28. package/dist/components/header/UnshareableDataWarning.d.ts +6 -0
  29. package/dist/components/header/UnshareableDataWarning.js +42 -0
  30. package/dist/components/header/UnshareableDataWarning.js.map +1 -0
  31. package/dist/components/header/getAnnotationsMenu.js +14 -14
  32. package/dist/components/header/getAnnotationsMenu.js.map +1 -1
  33. package/dist/components/import/ImportForm.js +1 -1
  34. package/dist/components/import/ImportForm.js.map +1 -1
  35. package/dist/components/import/ImportFormExamples.js +2 -6
  36. package/dist/components/import/ImportFormExamples.js.map +1 -1
  37. package/dist/components/import/util.js +3 -0
  38. package/dist/components/import/util.js.map +1 -1
  39. package/dist/components/msa/MSACanvasBlock.js +4 -5
  40. package/dist/components/msa/MSACanvasBlock.js.map +1 -1
  41. package/dist/components/msa/msaRaster.d.ts +13 -3
  42. package/dist/components/msa/msaRaster.js +173 -61
  43. package/dist/components/msa/msaRaster.js.map +1 -1
  44. package/dist/components/msa/renderHighlights.d.ts +39 -1
  45. package/dist/components/msa/renderHighlights.js +71 -6
  46. package/dist/components/msa/renderHighlights.js.map +1 -1
  47. package/dist/components/msa/renderMSABlock.d.ts +1 -2
  48. package/dist/components/msa/renderMSABlock.js +24 -28
  49. package/dist/components/msa/renderMSABlock.js.map +1 -1
  50. package/dist/components/msa/renderMSAMouseover.js +9 -30
  51. package/dist/components/msa/renderMSAMouseover.js.map +1 -1
  52. package/dist/components/renderCtx.d.ts +1 -1
  53. package/dist/components/tracks/TrackBlocks.js +10 -11
  54. package/dist/components/tracks/TrackBlocks.js.map +1 -1
  55. package/dist/components/tracks/drawTracks.d.ts +26 -8
  56. package/dist/components/tracks/drawTracks.js +123 -13
  57. package/dist/components/tracks/drawTracks.js.map +1 -1
  58. package/dist/components/tree/TreeCanvas.js +2 -3
  59. package/dist/components/tree/TreeCanvas.js.map +1 -1
  60. package/dist/components/tree/TreeCanvasBlock.js +4 -2
  61. package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
  62. package/dist/components/tree/TreeRuler.js +13 -3
  63. package/dist/components/tree/TreeRuler.js.map +1 -1
  64. package/dist/components/tree/renderTreeCanvas.d.ts +0 -1
  65. package/dist/components/tree/renderTreeCanvas.js +70 -30
  66. package/dist/components/tree/renderTreeCanvas.js.map +1 -1
  67. package/dist/components/tree/scaleBar.d.ts +10 -0
  68. package/dist/components/tree/scaleBar.js +24 -0
  69. package/dist/components/tree/scaleBar.js.map +1 -0
  70. package/dist/constants.d.ts +3 -1
  71. package/dist/constants.js +18 -1
  72. package/dist/constants.js.map +1 -1
  73. package/dist/fetchUtils.d.ts +7 -7
  74. package/dist/fetchUtils.js +4 -32
  75. package/dist/fetchUtils.js.map +1 -1
  76. package/dist/hierarchy.d.ts +2 -1
  77. package/dist/hierarchy.js +16 -10
  78. package/dist/hierarchy.js.map +1 -1
  79. package/dist/index.d.ts +2 -1
  80. package/dist/index.js +8 -2
  81. package/dist/index.js.map +1 -1
  82. package/dist/model/DataModel.js +5 -2
  83. package/dist/model/DataModel.js.map +1 -1
  84. package/dist/model.d.ts +454 -177
  85. package/dist/model.js +1180 -511
  86. package/dist/model.js.map +1 -1
  87. package/dist/renderToStaticMarkup.d.ts +11 -0
  88. package/dist/renderToStaticMarkup.js +30 -7
  89. package/dist/renderToStaticMarkup.js.map +1 -1
  90. package/dist/renderToSvg.js +25 -19
  91. package/dist/renderToSvg.js.map +1 -1
  92. package/dist/rowCoordinateCalculations.d.ts +25 -25
  93. package/dist/rowCoordinateCalculations.js +12 -36
  94. package/dist/rowCoordinateCalculations.js.map +1 -1
  95. package/dist/seqPosToGlobalCol.js +3 -1
  96. package/dist/seqPosToGlobalCol.js.map +1 -1
  97. package/dist/svgTestUtil.d.ts +144 -82
  98. package/dist/types.d.ts +97 -3
  99. package/dist/util.d.ts +11 -4
  100. package/dist/util.js +30 -10
  101. package/dist/util.js.map +1 -1
  102. package/dist/version.d.ts +1 -1
  103. package/dist/version.js +1 -1
  104. package/dist/wuss.d.ts +14 -0
  105. package/dist/wuss.js +55 -0
  106. package/dist/wuss.js.map +1 -0
  107. package/package.json +10 -8
  108. package/src/components/Loading.tsx +12 -2
  109. package/src/components/MSAViewer.tsx +106 -2
  110. package/src/components/ResizeHandles.tsx +31 -31
  111. package/src/components/dialogs/AnnotationFileDialog.tsx +162 -0
  112. package/src/components/dialogs/FeatureDialog.tsx +13 -9
  113. package/src/components/header/Header.tsx +4 -0
  114. package/src/components/header/HeaderStatusArea.tsx +0 -5
  115. package/src/components/header/LoadWarnings.tsx +49 -0
  116. package/src/components/header/MultiAlignmentSelector.tsx +0 -0
  117. package/src/components/header/TreeSettingsMenu.tsx +9 -1
  118. package/src/components/header/UnshareableDataWarning.tsx +58 -0
  119. package/src/components/header/getAnnotationsMenu.ts +16 -17
  120. package/src/components/import/ImportForm.tsx +2 -2
  121. package/src/components/import/ImportFormExamples.tsx +2 -6
  122. package/src/components/import/util.ts +3 -0
  123. package/src/components/msa/MSACanvasBlock.tsx +4 -5
  124. package/src/components/msa/msaRaster.ts +216 -59
  125. package/src/components/msa/renderHighlights.ts +103 -5
  126. package/src/components/msa/renderMSABlock.ts +25 -36
  127. package/src/components/msa/renderMSAMouseover.ts +9 -39
  128. package/src/components/renderCtx.ts +1 -0
  129. package/src/components/tracks/TrackBlocks.tsx +10 -13
  130. package/src/components/tracks/drawTracks.ts +165 -17
  131. package/src/components/tree/TreeCanvas.tsx +2 -3
  132. package/src/components/tree/TreeCanvasBlock.tsx +4 -2
  133. package/src/components/tree/TreeRuler.tsx +38 -3
  134. package/src/components/tree/renderTreeCanvas.ts +79 -35
  135. package/src/components/tree/scaleBar.ts +23 -0
  136. package/src/constants.ts +20 -1
  137. package/src/fetchUtils.ts +7 -40
  138. package/src/hierarchy.ts +20 -11
  139. package/src/index.ts +18 -2
  140. package/src/model/DataModel.ts +6 -2
  141. package/src/model.ts +1352 -616
  142. package/src/rawImports.d.ts +9 -0
  143. package/src/renderToStaticMarkup.ts +34 -7
  144. package/src/renderToSvg.tsx +33 -27
  145. package/src/rowCoordinateCalculations.ts +15 -59
  146. package/src/seqPosToGlobalCol.ts +3 -1
  147. package/src/types.ts +131 -4
  148. package/src/util.ts +31 -14
  149. package/src/version.ts +1 -1
  150. package/src/wuss.ts +62 -0
  151. package/dist/components/dialogs/InterProScanDialog.js +0 -137
  152. package/dist/components/dialogs/InterProScanDialog.js.map +0 -1
  153. package/dist/components/dialogs/InterProScanFileDialog.d.ts +0 -7
  154. package/dist/components/dialogs/InterProScanFileDialog.js +0 -64
  155. package/dist/components/dialogs/InterProScanFileDialog.js.map +0 -1
  156. package/dist/launchInterProScan.d.ts +0 -13
  157. package/dist/launchInterProScan.js +0 -110
  158. package/dist/launchInterProScan.js.map +0 -1
  159. package/dist/useColorContrast.d.ts +0 -10
  160. package/dist/useColorContrast.js +0 -13
  161. package/dist/useColorContrast.js.map +0 -1
  162. package/src/clustalX.test.ts +0 -74
  163. package/src/colSpace.test.ts +0 -67
  164. package/src/collapseLogic.test.ts +0 -115
  165. package/src/colorSchemes.docs.test.ts +0 -57
  166. package/src/columnCounts.test.ts +0 -73
  167. package/src/columnStats.test.ts +0 -94
  168. package/src/columnTracks.test.ts +0 -97
  169. package/src/components/dialogs/InterProScanDialog.tsx +0 -214
  170. package/src/components/dialogs/InterProScanFileDialog.tsx +0 -138
  171. package/src/components/minimap/minimapLayout.test.ts +0 -71
  172. package/src/components/msa/msaRaster.test.ts +0 -145
  173. package/src/components/msa/renderBoxFeatureCanvasBlock.test.ts +0 -51
  174. package/src/components/tree/renderTreeCanvas.test.ts +0 -296
  175. package/src/dataInitialized.test.ts +0 -42
  176. package/src/domainBands.test.ts +0 -122
  177. package/src/domainLetterColor.test.ts +0 -80
  178. package/src/exportFileName.test.ts +0 -40
  179. package/src/fetchUtils.test.ts +0 -83
  180. package/src/flatToTree.test.ts +0 -51
  181. package/src/hierarchy.test.ts +0 -228
  182. package/src/highlights.test.ts +0 -70
  183. package/src/impgRender.test.tsx +0 -68
  184. package/src/largeTree.test.ts +0 -67
  185. package/src/launchInterProScan.ts +0 -167
  186. package/src/loadErrorDisplay.test.tsx +0 -102
  187. package/src/modelFilehandleLoaders.test.ts +0 -137
  188. package/src/modelReset.test.ts +0 -120
  189. package/src/neighborJoining.test.ts +0 -164
  190. package/src/parseAsn1.test.ts +0 -36
  191. package/src/propertyConservation.test.ts +0 -36
  192. package/src/renderColumnTracksSvg.test.tsx +0 -108
  193. package/src/renderDomainsSvg.test.tsx +0 -118
  194. package/src/renderHighlightsSvg.test.tsx +0 -85
  195. package/src/renderMinimapSvg.test.tsx +0 -62
  196. package/src/renderRasterSvg.test.tsx +0 -144
  197. package/src/renderSequenceLogoSvg.test.tsx +0 -165
  198. package/src/renderToSvg.test.tsx +0 -65
  199. package/src/reset.test.ts +0 -61
  200. package/src/rowCoordinateCalculations.test.ts +0 -211
  201. package/src/rowSpace.test.ts +0 -46
  202. package/src/segmentOverlay.test.ts +0 -70
  203. package/src/seqPosToGlobalCol.test.ts +0 -59
  204. package/src/sequenceLogo.test.ts +0 -88
  205. package/src/setFontSize.test.ts +0 -33
  206. package/src/stripDefault.test.ts +0 -48
  207. package/src/stripDefaultSnapshot.test.ts +0 -87
  208. package/src/treeMetadata.test.ts +0 -39
  209. package/src/useCanvasAutorun.test.tsx +0 -98
  210. package/src/useColorContrast.ts +0 -18
  211. package/src/viewChrome.test.tsx +0 -134
  212. package/src/wheelZoom.test.ts +0 -46
@@ -1,20 +1,9 @@
1
- import {
2
- clickColor,
3
- hoverColor,
4
- multiRowHoverColor,
5
- referenceColor,
6
- } from '../overlayColors.ts'
7
- import { renderHighlights } from './renderHighlights.ts'
1
+ import { clickColor, hoverColor, multiRowHoverColor } from '../overlayColors.ts'
2
+ import { renderPersistentHighlights } from './renderHighlights.ts'
8
3
 
9
4
  import type { MsaViewModel } from '../../model.ts'
10
5
  import type { Theme } from '@mui/material'
11
6
 
12
- // the persistent highlightColumns overlay: a stronger fill plus a solid border
13
- // so a domain/motif band reads clearly over the colored alignment cells (the
14
- // faint hover-style wash alone is invisible against clustalx coloring)
15
- const highlightColumnsFill = 'rgba(255,140,0,0.28)'
16
- const highlightColumnsBorder = 'rgba(210,90,0,0.95)'
17
-
18
7
  export function renderMouseover({
19
8
  ctx,
20
9
  model,
@@ -36,9 +25,7 @@ export function renderMouseover({
36
25
  mouseClickRow,
37
26
  mouseClickCol,
38
27
  highResScaleFactor,
39
- referenceRowIndex,
40
28
  hoveredRowIndices,
41
- highlightedColumnRuns,
42
29
  } = model
43
30
  ctx.resetTransform()
44
31
  ctx.clearRect(0, 0, width * highResScaleFactor, height * highResScaleFactor)
@@ -53,30 +40,7 @@ export function renderMouseover({
53
40
  ctx.fillRect(index * colWidth + scrollX, 0, colWidth, height)
54
41
  }
55
42
 
56
- // the reference row (relativeTo) stays lit, as does every tip under a hovered
57
- // tree node
58
- if (referenceRowIndex !== undefined) {
59
- ctx.fillStyle = referenceColor
60
- rowBand(referenceRowIndex)
61
- }
62
- ctx.fillStyle = multiRowHoverColor
63
- for (const rowIndex of hoveredRowIndices) {
64
- rowBand(rowIndex)
65
- }
66
-
67
- // each contiguous run of highlighted columns draws as one filled, bordered
68
- // band, so a domain/motif highlight stays visible over the alignment
69
- ctx.lineWidth = 2
70
- for (const { start, end } of highlightedColumnRuns) {
71
- const x = start * colWidth + scrollX
72
- const w = (end - start + 1) * colWidth
73
- ctx.fillStyle = highlightColumnsFill
74
- ctx.fillRect(x, 0, w, height)
75
- ctx.strokeStyle = highlightColumnsBorder
76
- ctx.strokeRect(x, 0, w, height)
77
- }
78
-
79
- renderHighlights({
43
+ renderPersistentHighlights({
80
44
  ctx,
81
45
  model,
82
46
  theme,
@@ -86,6 +50,12 @@ export function renderMouseover({
86
50
  height,
87
51
  })
88
52
 
53
+ // every tip under a hovered tree node stays lit
54
+ ctx.fillStyle = multiRowHoverColor
55
+ for (const rowIndex of hoveredRowIndices) {
56
+ rowBand(rowIndex)
57
+ }
58
+
89
59
  ctx.fillStyle = hoverColor
90
60
  if (mouseCol !== undefined) {
91
61
  colBand(mouseCol)
@@ -19,6 +19,7 @@ export type RenderCtx = Pick<
19
19
  | 'lineWidth'
20
20
  | 'measureText'
21
21
  | 'moveTo'
22
+ | 'quadraticCurveTo'
22
23
  | 'resetTransform'
23
24
  | 'restore'
24
25
  | 'save'
@@ -1,9 +1,9 @@
1
1
  import React from 'react'
2
2
 
3
+ import { useTheme } from '@mui/material'
3
4
  import { observer } from 'mobx-react'
4
5
 
5
6
  import { useCanvasAutorun } from '../../useCanvasAutorun.ts'
6
- import { useColorContrast } from '../../useColorContrast.ts'
7
7
  import { TrackResizeHandle } from '../ResizeHandles.tsx'
8
8
  import { drawTrackBlock } from './drawTracks.ts'
9
9
 
@@ -19,11 +19,9 @@ const TrackBlock = observer(function ({
19
19
  track: BasicTrack
20
20
  offsetX: number
21
21
  }) {
22
- const { blockSize, colorScheme, highResScaleFactor } = model
23
- const { height, customColorScheme } = track.model
24
- const { theme, contrastScheme } = useColorContrast(
25
- customColorScheme ?? colorScheme,
26
- )
22
+ const { blockSize, highResScaleFactor } = model
23
+ const { height } = track.model
24
+ const theme = useTheme()
27
25
  const canvasWidth = blockSize * highResScaleFactor
28
26
  const canvasHeight = height * highResScaleFactor
29
27
 
@@ -31,11 +29,11 @@ const TrackBlock = observer(function ({
31
29
  draw: ctx => {
32
30
  ctx.resetTransform()
33
31
  ctx.clearRect(0, 0, canvasWidth, canvasHeight)
34
- drawTrackBlock({ model, ctx, track, offsetX, theme, contrastScheme })
32
+ drawTrackBlock({ model, ctx, track, offsetX, theme })
35
33
  },
36
34
  width: canvasWidth,
37
35
  height: canvasHeight,
38
- deps: [model, track, offsetX, theme, contrastScheme],
36
+ deps: [model, track, offsetX, theme],
39
37
  })
40
38
 
41
39
  return (
@@ -72,11 +70,10 @@ const TrackBlocks = observer(function ({
72
70
  // last columns appear in the tracks but not in the alignment, and the hovered
73
71
  // -column indicator (sized to the canvas) stops short of the track it crosses
74
72
  const { blocksX, msaCanvasWidth, scrollX } = model
75
- const { kind, height, data } = track.model
73
+ const { kind, height, data, arcs } = track.model
76
74
 
77
- // a text track with no data draws nothing, and an empty row of its own would
78
- // still take up vertical space
79
- if (kind === 'text' && !data) {
75
+ // a track with nothing to draw would still take up its vertical space
76
+ if ((kind === 'text' && !data) || (kind === 'arc' && !arcs?.length)) {
80
77
  return null
81
78
  }
82
79
 
@@ -103,7 +100,7 @@ const TrackBlocks = observer(function ({
103
100
  <TrackBlock key={bx} model={model} track={track} offsetX={bx} />
104
101
  ))}
105
102
  </div>
106
- <TrackResizeHandle model={model} kind={kind} />
103
+ <TrackResizeHandle model={model} track={track} />
107
104
  </div>
108
105
  )
109
106
  })
@@ -1,10 +1,11 @@
1
1
  import { columnLogoStack } from '../../sequenceLogo.ts'
2
2
  import { setFontSize } from '../../setFontSize.ts'
3
+ import { contrastTextFn } from '../../util.ts'
3
4
  import { visibleColRange } from '../msa/visibleColRange.ts'
4
5
 
5
6
  import type { ColumnCounts } from '../../columnCounts.ts'
6
7
  import type { MsaViewModel } from '../../model.ts'
7
- import type { BasicTrack } from '../../types.ts'
8
+ import type { Arc, BasicTrack } from '../../types.ts'
8
9
  import type { RenderCtx } from '../renderCtx.ts'
9
10
  import type { Theme } from '@mui/material'
10
11
 
@@ -52,13 +53,65 @@ export function barTrackValues(model: MsaViewModel, trackId: string) {
52
53
  : model.conservation
53
54
  }
54
55
 
56
+ // An arc's apex scales with how far it reaches, so a short helix stays shallow
57
+ // and a long-range pair sweeps the full track height. The scale is sqrt rather
58
+ // than linear because a contact map's spans are heavily skewed towards short
59
+ // ones, which linear scaling flattens into a smear along the baseline.
60
+ export function drawArcs({
61
+ ctx,
62
+ arcs,
63
+ color,
64
+ colWidth,
65
+ trackHeight,
66
+ offsetX,
67
+ blockSize,
68
+ }: {
69
+ ctx: RenderCtx
70
+ arcs: Arc[] | undefined
71
+ color: string
72
+ colWidth: number
73
+ trackHeight: number
74
+ offsetX: number
75
+ blockSize: number
76
+ }) {
77
+ if (!arcs?.length) {
78
+ return
79
+ }
80
+ const { xStart, xEnd } = visibleColRange({
81
+ offsetX,
82
+ blockWidth: blockSize,
83
+ colWidth,
84
+ })
85
+ let maxSpan = 1
86
+ for (const arc of arcs) {
87
+ maxSpan = Math.max(maxSpan, arc.end - arc.start)
88
+ }
89
+ // a full-height apex would put the stroke half outside the track
90
+ const apexRoom = trackHeight - 1
91
+ ctx.lineWidth = 1
92
+ for (const arc of arcs) {
93
+ // an arc that spans the block draws even though neither foot is inside it
94
+ if (arc.end < xStart || arc.start > xEnd) {
95
+ continue
96
+ }
97
+ const x1 = (arc.start + 0.5) * colWidth
98
+ const x2 = (arc.end + 0.5) * colWidth
99
+ const apex = apexRoom * Math.sqrt((arc.end - arc.start) / maxSpan)
100
+ ctx.strokeStyle = arc.color ?? color
101
+ ctx.beginPath()
102
+ ctx.moveTo(x1, trackHeight)
103
+ ctx.quadraticCurveTo((x1 + x2) / 2, trackHeight - 2 * apex, x2, trackHeight)
104
+ ctx.stroke()
105
+ }
106
+ }
107
+
55
108
  export function drawTextTrackContent({
56
109
  ctx,
57
110
  data,
58
111
  colorScheme,
59
- contrastScheme,
112
+ contrastText,
60
113
  bgColor,
61
- textColor,
114
+ drawLetters,
62
115
  colWidth,
63
116
  rowHeight,
64
117
  offsetX,
@@ -67,9 +120,9 @@ export function drawTextTrackContent({
67
120
  ctx: RenderCtx
68
121
  data: string | undefined
69
122
  colorScheme: Record<string, string>
70
- contrastScheme: Record<string, string>
123
+ contrastText: (color: string | undefined) => string
71
124
  bgColor: boolean
72
- textColor: string
125
+ drawLetters: boolean
73
126
  colWidth: number
74
127
  rowHeight: number
75
128
  offsetX: number
@@ -81,7 +134,6 @@ export function drawTextTrackContent({
81
134
  colWidth,
82
135
  })
83
136
  const str = data?.slice(xStart, xEnd)
84
- const drawLetters = rowHeight >= 10 && colWidth >= rowHeight / 2
85
137
 
86
138
  for (let i = 0; str && i < str.length; i++) {
87
139
  const letter = str[i]!
@@ -93,11 +145,73 @@ export function drawTextTrackContent({
93
145
  ctx.fillRect(x, 0, colWidth, rowHeight)
94
146
  }
95
147
  if (drawLetters) {
96
- // a letter on a colored tile needs the tile's contrast color; otherwise it
97
- // sits on the plain background and takes the theme's text color
98
- ctx.fillStyle = fill ? (contrastScheme[upper] ?? 'black') : textColor
99
- ctx.fillText(letter, x + colWidth / 2, rowHeight / 2 + 1)
148
+ // a letter on a colored tile needs that tile's contrast color; otherwise
149
+ // it sits on the plain background and takes the theme's text color
150
+ ctx.fillStyle = contrastText(fill)
151
+ ctx.fillText(letter, x + colWidth / 2, rowHeight / 2)
152
+ }
153
+ }
154
+ }
155
+
156
+ // Column numbers every `step` columns, with a tick under each. The step is the
157
+ // smallest 1/2/5 x 10^n that leaves room for a label, so a zoom changes how many
158
+ // numbers appear rather than crowding them.
159
+ export function rulerStep(colWidth: number, minPixels = 55) {
160
+ let step = 1
161
+ const mantissas = [1, 2, 5]
162
+ for (let i = 0; step * colWidth < minPixels; i++) {
163
+ step = mantissas[i % 3]! * 10 ** Math.floor(i / 3)
164
+ }
165
+ return step
166
+ }
167
+
168
+ const RULER_FONT_SIZE = 10
169
+ const TICK_HEIGHT = 4
170
+
171
+ export function drawColumnRuler({
172
+ ctx,
173
+ numColumns,
174
+ label,
175
+ textColor,
176
+ colWidth,
177
+ trackHeight,
178
+ offsetX,
179
+ blockSize,
180
+ }: {
181
+ ctx: RenderCtx
182
+ numColumns: number
183
+ label: (col: number) => string | undefined
184
+ textColor: string
185
+ colWidth: number
186
+ trackHeight: number
187
+ offsetX: number
188
+ blockSize: number
189
+ }) {
190
+ const { xStart, xEnd } = visibleColRange({
191
+ offsetX,
192
+ blockWidth: blockSize,
193
+ colWidth,
194
+ })
195
+ const step = rulerStep(colWidth)
196
+ const end = Math.min(xEnd, numColumns)
197
+ setFontSize(ctx, RULER_FONT_SIZE)
198
+ ctx.textAlign = 'center'
199
+ ctx.textBaseline = 'alphabetic'
200
+ ctx.fillStyle = textColor
201
+ ctx.strokeStyle = textColor
202
+ ctx.lineWidth = 1
203
+
204
+ for (let col = Math.ceil(xStart / step) * step; col < end; col += step) {
205
+ const text = label(col)
206
+ if (text === undefined) {
207
+ continue
100
208
  }
209
+ const x = (col + 0.5) * colWidth
210
+ ctx.fillText(text, x, trackHeight - TICK_HEIGHT - 2)
211
+ ctx.beginPath()
212
+ ctx.moveTo(x, trackHeight - TICK_HEIGHT)
213
+ ctx.lineTo(x, trackHeight)
214
+ ctx.stroke()
101
215
  }
102
216
  }
103
217
 
@@ -197,7 +311,6 @@ export function drawTrackBlock({
197
311
  offsetX,
198
312
  offsetY = 0,
199
313
  theme,
200
- contrastScheme,
201
314
  blockSizeXOverride,
202
315
  highResScaleFactorOverride,
203
316
  }: {
@@ -207,7 +320,6 @@ export function drawTrackBlock({
207
320
  offsetX: number
208
321
  offsetY?: number
209
322
  theme: Theme
210
- contrastScheme: Record<string, string>
211
323
  blockSizeXOverride?: number
212
324
  highResScaleFactorOverride?: number
213
325
  }) {
@@ -219,6 +331,7 @@ export function drawTrackBlock({
219
331
  colorScheme: modelColorScheme,
220
332
  fontSize,
221
333
  rowHeight,
334
+ showMsaLetters,
222
335
  alphabetMaxBits,
223
336
  highResScaleFactor,
224
337
  } = model
@@ -227,6 +340,8 @@ export function drawTrackBlock({
227
340
  kind,
228
341
  height: trackHeight,
229
342
  barColor,
343
+ arcColor,
344
+ arcs,
230
345
  customColorScheme,
231
346
  data,
232
347
  } = track.model
@@ -251,6 +366,18 @@ export function drawTrackBlock({
251
366
  })
252
367
  break
253
368
  }
369
+ case 'arc': {
370
+ drawArcs({
371
+ ctx,
372
+ arcs,
373
+ color: arcColor ?? theme.palette.text.primary,
374
+ colWidth,
375
+ trackHeight,
376
+ offsetX,
377
+ blockSize: blockSizeX,
378
+ })
379
+ break
380
+ }
254
381
  case 'logo': {
255
382
  drawSequenceLogo({
256
383
  ctx,
@@ -265,16 +392,40 @@ export function drawTrackBlock({
265
392
  })
266
393
  break
267
394
  }
395
+ case 'ruler': {
396
+ const { relativeTo, numColumns } = model
397
+ drawColumnRuler({
398
+ ctx,
399
+ numColumns,
400
+ // numbered by the reference row's own residues when the alignment is
401
+ // drawn relative to one, which is what the rest of the view counts in
402
+ label: relativeTo
403
+ ? col => {
404
+ const pos = model.visibleColToSeqPosOneBased(relativeTo, col)
405
+ return pos === undefined ? undefined : `${pos}`
406
+ }
407
+ : col => `${col + 1}`,
408
+ textColor,
409
+ colWidth,
410
+ trackHeight,
411
+ offsetX,
412
+ blockSize: blockSizeX,
413
+ })
414
+ break
415
+ }
268
416
  case 'text': {
269
417
  ctx.textAlign = 'center'
418
+ ctx.textBaseline = 'middle'
270
419
  setFontSize(ctx, fontSize)
271
420
  drawTextTrackContent({
272
421
  ctx,
273
422
  data,
274
423
  colorScheme: customColorScheme ?? modelColorScheme,
275
- contrastScheme,
424
+ contrastText: contrastTextFn(theme),
276
425
  bgColor,
277
- textColor,
426
+ // a text track is one alignment row tall, so it shows letters exactly
427
+ // when the alignment does
428
+ drawLetters: showMsaLetters,
278
429
  colWidth,
279
430
  rowHeight,
280
431
  offsetX,
@@ -295,7 +446,6 @@ export function renderAllTracks({
295
446
  model,
296
447
  ctx,
297
448
  offsetX,
298
- contrastScheme,
299
449
  theme,
300
450
  blockSizeXOverride,
301
451
  highResScaleFactorOverride,
@@ -303,7 +453,6 @@ export function renderAllTracks({
303
453
  model: MsaViewModel
304
454
  ctx: RenderCtx
305
455
  offsetX: number
306
- contrastScheme: Record<string, string>
307
456
  theme: Theme
308
457
  blockSizeXOverride?: number
309
458
  highResScaleFactorOverride?: number
@@ -317,7 +466,6 @@ export function renderAllTracks({
317
466
  offsetX,
318
467
  offsetY: currentY,
319
468
  theme,
320
- contrastScheme,
321
469
  blockSizeXOverride,
322
470
  highResScaleFactorOverride,
323
471
  })
@@ -7,13 +7,12 @@ import { useCanvasAutorun } from '../../useCanvasAutorun.ts'
7
7
  import { useWheelScroll } from '../../useWheelScroll.ts'
8
8
  import { referenceColor, treeHoverColor } from '../overlayColors.ts'
9
9
  import TreeCanvasBlock from './TreeCanvasBlock.tsx'
10
- import { padding } from './renderTreeCanvas.ts'
11
10
 
12
11
  import type { MsaViewModel } from '../../model.ts'
13
12
 
14
13
  const TreeCanvas = observer(function ({ model }: { model: MsaViewModel }) {
15
14
  const ref = useRef<HTMLDivElement>(null)
16
- const { treeWidth, height, blocksY, treeAreaWidth, scrollY } = model
15
+ const { height, blocksY, treeAreaWidth, scrollY } = model
17
16
  const onScrollY = useCallback(
18
17
  (d: number) => {
19
18
  model.doScrollY(d)
@@ -77,7 +76,7 @@ const TreeCanvas = observer(function ({ model }: { model: MsaViewModel }) {
77
76
  style={{
78
77
  height,
79
78
  position: 'relative',
80
- width: treeWidth + padding,
79
+ width: treeAreaWidth,
81
80
  }}
82
81
  >
83
82
  {/* one transform for the whole block set, matching MSACanvas: the tree
@@ -8,7 +8,7 @@ import { createPortal } from 'react-dom'
8
8
  import { useCanvasAutorun } from '../../useCanvasAutorun.ts'
9
9
  import TreeBranchMenu from './TreeBranchMenu.tsx'
10
10
  import TreeNodeMenu from './TreeNodeMenu.tsx'
11
- import { padding, renderTreeCanvas } from './renderTreeCanvas.ts'
11
+ import { renderTreeCanvas } from './renderTreeCanvas.ts'
12
12
  import { useTreeHover } from './useTreeHover.ts'
13
13
 
14
14
  import type { MsaViewModel } from '../../model.ts'
@@ -58,7 +58,9 @@ const TreeCanvasBlock = observer(function ({
58
58
  useTreeHover({ model, offsetY })
59
59
 
60
60
  const { treeAreaWidth, blockSize, highResScaleFactor } = model
61
- const width = treeAreaWidth + padding
61
+ // TreePanel clips to treeAreaWidth, so anything wider is backing store the
62
+ // viewer never sees -- it was 600px of it per block, at dpr squared
63
+ const width = treeAreaWidth
62
64
  const height = blockSize
63
65
 
64
66
  const canvasWidth = width * highResScaleFactor
@@ -1,16 +1,51 @@
1
1
  import React from 'react'
2
2
 
3
+ import { useTheme } from '@mui/material'
3
4
  import { observer } from 'mobx-react'
4
5
 
6
+ import { scaleBarLength } from './scaleBar.ts'
7
+
5
8
  import type { MsaViewModel } from '../../model.ts'
6
9
 
10
+ const BAR_HEIGHT = 22
11
+
7
12
  // The gutter above the tree panel. It spans the resize handle too, so whatever
8
13
  // follows it in the top row starts exactly where the alignment canvas does --
9
- // MainArea has TreePanel + VerticalResizeHandle before the alignment.
14
+ // MainArea has TreePanel + VerticalResizeHandle before the alignment. A
15
+ // phylogram fills it with a scale bar: without one the branch lengths are drawn
16
+ // to a scale nothing states.
10
17
  const TreeRuler = observer(({ model }: { model: MsaViewModel }) => {
11
- const { treeAreaWidth, resizeHandleWidth } = model
18
+ const { treeAreaWidth, resizeHandleWidth, marginLeft, pxPerBranchLength } =
19
+ model
20
+ const theme = useTheme()
21
+ const width = treeAreaWidth + resizeHandleWidth
22
+ const bar = scaleBarLength(pxPerBranchLength, treeAreaWidth - marginLeft * 2)
23
+
12
24
  return (
13
- <div style={{ flexShrink: 0, width: treeAreaWidth + resizeHandleWidth }} />
25
+ <div style={{ flexShrink: 0, width }}>
26
+ {bar ? (
27
+ <svg
28
+ width={width}
29
+ height={BAR_HEIGHT}
30
+ style={{ display: 'block' }}
31
+ color={theme.palette.text.primary}
32
+ >
33
+ <text
34
+ x={marginLeft}
35
+ y={BAR_HEIGHT - 9}
36
+ fontSize={10}
37
+ fill="currentColor"
38
+ >
39
+ {bar.label}
40
+ </text>
41
+ <path
42
+ d={`M${marginLeft} ${BAR_HEIGHT - 7} v6 h${bar.px} v-6`}
43
+ fill="none"
44
+ stroke="currentColor"
45
+ />
46
+ </svg>
47
+ ) : null}
48
+ </div>
14
49
  )
15
50
  })
16
51