react-msaview 9.0.0 → 9.1.1

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 (352) hide show
  1. package/README.md +19 -10
  2. package/bundle/index.js +111 -115
  3. package/bundle/index.js.map +4 -4
  4. package/dist/blosum62.d.ts +3 -0
  5. package/dist/blosum62.js +43 -0
  6. package/dist/blosum62.js.map +1 -0
  7. package/dist/colorSchemes.js +2 -3
  8. package/dist/colorSchemes.js.map +1 -1
  9. package/dist/columnCounts.d.ts +0 -1
  10. package/dist/columnCounts.js +1 -1
  11. package/dist/columnCounts.js.map +1 -1
  12. package/dist/components/Loading.d.ts +3 -2
  13. package/dist/components/Loading.js +7 -7
  14. package/dist/components/Loading.js.map +1 -1
  15. package/dist/components/MSAView.d.ts +3 -2
  16. package/dist/components/MSAView.js +7 -4
  17. package/dist/components/MSAView.js.map +1 -1
  18. package/dist/components/MSAViewer.d.ts +1941 -4
  19. package/dist/components/MSAViewer.js +144 -201
  20. package/dist/components/MSAViewer.js.map +1 -1
  21. package/dist/components/MetadataTable.d.ts +4 -1
  22. package/dist/components/MetadataTable.js +55 -16
  23. package/dist/components/MetadataTable.js.map +1 -1
  24. package/dist/components/ResizeHandles.js +3 -2
  25. package/dist/components/ResizeHandles.js.map +1 -1
  26. package/dist/components/Track.js +8 -1
  27. package/dist/components/Track.js.map +1 -1
  28. package/dist/components/controls.d.ts +11 -0
  29. package/dist/components/controls.js +49 -0
  30. package/dist/components/controls.js.map +1 -0
  31. package/dist/components/dialogs/AnnotationFileDialog.js +12 -18
  32. package/dist/components/dialogs/AnnotationFileDialog.js.map +1 -1
  33. package/dist/components/dialogs/ExportSVGDialog.js +5 -7
  34. package/dist/components/dialogs/ExportSVGDialog.js.map +1 -1
  35. package/dist/components/dialogs/FeatureDialog.js +2 -2
  36. package/dist/components/dialogs/FeatureDialog.js.map +1 -1
  37. package/dist/components/{header/TreeSettingsMenu.d.ts → dialogs/GapThresholdDialog.d.ts} +3 -2
  38. package/dist/components/dialogs/GapThresholdDialog.js +15 -0
  39. package/dist/components/dialogs/GapThresholdDialog.js.map +1 -0
  40. package/dist/components/{header → dialogs}/GappynessSlider.d.ts +1 -1
  41. package/dist/components/{header → dialogs}/GappynessSlider.js +3 -10
  42. package/dist/components/dialogs/GappynessSlider.js.map +1 -0
  43. package/dist/components/{header → dialogs}/GoToBox.d.ts +4 -2
  44. package/dist/components/dialogs/GoToBox.js +48 -0
  45. package/dist/components/dialogs/GoToBox.js.map +1 -0
  46. package/dist/components/dialogs/GoToDialog.d.ts +6 -0
  47. package/dist/components/dialogs/GoToDialog.js +10 -0
  48. package/dist/components/dialogs/GoToDialog.js.map +1 -0
  49. package/dist/components/dialogs/HelpDialog.d.ts +6 -0
  50. package/dist/components/dialogs/HelpDialog.js +30 -0
  51. package/dist/components/dialogs/HelpDialog.js.map +1 -0
  52. package/dist/components/dialogs/MetadataDialog.js +11 -3
  53. package/dist/components/dialogs/MetadataDialog.js.map +1 -1
  54. package/dist/components/dialogs/goTo.d.ts +5 -0
  55. package/dist/components/{header → dialogs}/goTo.js +1 -1
  56. package/dist/components/dialogs/goTo.js.map +1 -0
  57. package/dist/components/dialogs/metadataLabels.d.ts +4 -0
  58. package/dist/components/dialogs/metadataLabels.js +52 -0
  59. package/dist/components/dialogs/metadataLabels.js.map +1 -0
  60. package/dist/components/dialogs/metadataRecord.d.ts +1 -0
  61. package/dist/components/dialogs/metadataRecord.js +38 -0
  62. package/dist/components/dialogs/metadataRecord.js.map +1 -0
  63. package/dist/components/header/Header.d.ts +6 -2
  64. package/dist/components/header/Header.js +30 -34
  65. package/dist/components/header/Header.js.map +1 -1
  66. package/dist/components/header/HiddenColumnsChip.d.ts +6 -0
  67. package/dist/components/header/HiddenColumnsChip.js +15 -0
  68. package/dist/components/header/HiddenColumnsChip.js.map +1 -0
  69. package/dist/components/header/{FileMenu.d.ts → MenuBar.d.ts} +3 -3
  70. package/dist/components/header/MenuBar.js +33 -0
  71. package/dist/components/header/MenuBar.js.map +1 -0
  72. package/dist/components/header/ScrollZoomCheckbox.d.ts +7 -0
  73. package/dist/components/header/ScrollZoomCheckbox.js +19 -0
  74. package/dist/components/header/ScrollZoomCheckbox.js.map +1 -0
  75. package/dist/components/header/ZoomControls.js +12 -48
  76. package/dist/components/header/ZoomControls.js.map +1 -1
  77. package/dist/components/header/colorSchemeMenuItems.d.ts +4 -0
  78. package/dist/components/header/colorSchemeMenuItems.js +106 -0
  79. package/dist/components/header/colorSchemeMenuItems.js.map +1 -0
  80. package/dist/components/header/getAnnotationsMenu.d.ts +3 -30
  81. package/dist/components/header/getAnnotationsMenu.js +48 -54
  82. package/dist/components/header/getAnnotationsMenu.js.map +1 -1
  83. package/dist/components/header/menuBarItems.d.ts +11 -0
  84. package/dist/components/header/menuBarItems.js +277 -0
  85. package/dist/components/header/menuBarItems.js.map +1 -0
  86. package/dist/components/import/ImportForm.js +12 -6
  87. package/dist/components/import/ImportForm.js.map +1 -1
  88. package/dist/components/import/data/seq2.d.ts +0 -1
  89. package/dist/components/import/data/seq2.js +0 -17
  90. package/dist/components/import/data/seq2.js.map +1 -1
  91. package/dist/components/minimap/Minimap.js +7 -3
  92. package/dist/components/minimap/Minimap.js.map +1 -1
  93. package/dist/components/minimap/MinimapSVG.js +2 -1
  94. package/dist/components/minimap/MinimapSVG.js.map +1 -1
  95. package/dist/components/minimap/minimapLayout.d.ts +12 -0
  96. package/dist/components/minimap/minimapLayout.js +20 -0
  97. package/dist/components/minimap/minimapLayout.js.map +1 -1
  98. package/dist/components/msa/MSACanvas.js +28 -10
  99. package/dist/components/msa/MSACanvas.js.map +1 -1
  100. package/dist/components/msa/MSACanvasBlock.js +17 -16
  101. package/dist/components/msa/MSACanvasBlock.js.map +1 -1
  102. package/dist/components/msa/MSAMouseoverCanvas.js +2 -2
  103. package/dist/components/msa/MSAMouseoverCanvas.js.map +1 -1
  104. package/dist/components/msa/WheelZoomHint.d.ts +7 -0
  105. package/dist/components/msa/WheelZoomHint.js +38 -0
  106. package/dist/components/msa/WheelZoomHint.js.map +1 -0
  107. package/dist/components/msa/drawFeatureSpans.d.ts +50 -11
  108. package/dist/components/msa/drawFeatureSpans.js +99 -56
  109. package/dist/components/msa/drawFeatureSpans.js.map +1 -1
  110. package/dist/components/msa/msaKeys.js +3 -0
  111. package/dist/components/msa/msaKeys.js.map +1 -1
  112. package/dist/components/msa/msaRaster.d.ts +14 -2
  113. package/dist/components/msa/msaRaster.js +20 -10
  114. package/dist/components/msa/msaRaster.js.map +1 -1
  115. package/dist/components/msa/renderBoxFeatureCanvasBlock.d.ts +9 -0
  116. package/dist/components/msa/renderBoxFeatureCanvasBlock.js +39 -8
  117. package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
  118. package/dist/components/msa/renderHighlights.d.ts +2 -5
  119. package/dist/components/msa/renderHighlights.js +5 -6
  120. package/dist/components/msa/renderHighlights.js.map +1 -1
  121. package/dist/components/msa/renderMSABlock.d.ts +6 -1
  122. package/dist/components/msa/renderMSABlock.js +44 -17
  123. package/dist/components/msa/renderMSABlock.js.map +1 -1
  124. package/dist/components/msa/renderMSAMouseover.js +2 -1
  125. package/dist/components/msa/renderMSAMouseover.js.map +1 -1
  126. package/dist/components/msa/tileColor.js +7 -0
  127. package/dist/components/msa/tileColor.js.map +1 -1
  128. package/dist/components/renderCtx.d.ts +1 -1
  129. package/dist/components/rowpanels/RowPanelBlock.js +2 -2
  130. package/dist/components/rowpanels/RowPanelBlock.js.map +1 -1
  131. package/dist/components/rowpanels/renderFeaturePanel.d.ts +5 -1
  132. package/dist/components/rowpanels/renderFeaturePanel.js +12 -5
  133. package/dist/components/rowpanels/renderFeaturePanel.js.map +1 -1
  134. package/dist/components/settings/MenuItemsForm.d.ts +10 -0
  135. package/dist/components/settings/MenuItemsForm.js +80 -0
  136. package/dist/components/settings/MenuItemsForm.js.map +1 -0
  137. package/dist/components/settings/SettingsPanel.d.ts +12 -0
  138. package/dist/components/settings/SettingsPanel.js +92 -0
  139. package/dist/components/settings/SettingsPanel.js.map +1 -0
  140. package/dist/components/{header/ZoomMenu.d.ts → settings/TrackList.d.ts} +2 -2
  141. package/dist/components/settings/TrackList.js +55 -0
  142. package/dist/components/settings/TrackList.js.map +1 -0
  143. package/dist/components/tracks/TrackBlocks.js +2 -2
  144. package/dist/components/tracks/TrackBlocks.js.map +1 -1
  145. package/dist/components/tracks/drawTracks.d.ts +1 -53
  146. package/dist/components/tracks/drawTracks.js +5 -5
  147. package/dist/components/tracks/drawTracks.js.map +1 -1
  148. package/dist/components/tree/CladeLabels.d.ts +1 -1
  149. package/dist/components/tree/CladeLabels.js +22 -34
  150. package/dist/components/tree/CladeLabels.js.map +1 -1
  151. package/dist/components/tree/TreeCanvasBlock.js +2 -2
  152. package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
  153. package/dist/components/tree/TreeMenu.js +4 -6
  154. package/dist/components/tree/TreeMenu.js.map +1 -1
  155. package/dist/components/tree/TreeOverview.js +2 -2
  156. package/dist/components/tree/TreeOverview.js.map +1 -1
  157. package/dist/components/tree/cladeBrackets.d.ts +17 -10
  158. package/dist/components/tree/cladeBrackets.js +33 -3
  159. package/dist/components/tree/cladeBrackets.js.map +1 -1
  160. package/dist/components/tree/renderTreeCanvas.js +2 -8
  161. package/dist/components/tree/renderTreeCanvas.js.map +1 -1
  162. package/dist/components/tree/renderTreeOverview.js +4 -11
  163. package/dist/components/tree/renderTreeOverview.js.map +1 -1
  164. package/dist/constants.d.ts +4 -1
  165. package/dist/constants.js +7 -1
  166. package/dist/constants.js.map +1 -1
  167. package/dist/darkReader.d.ts +7 -0
  168. package/dist/darkReader.js +42 -0
  169. package/dist/darkReader.js.map +1 -0
  170. package/dist/element.js +25 -53
  171. package/dist/element.js.map +1 -1
  172. package/dist/exportLayout.d.ts +39 -0
  173. package/dist/exportLayout.js +78 -0
  174. package/dist/exportLayout.js.map +1 -0
  175. package/dist/featurePanels.d.ts +37 -0
  176. package/dist/featurePanels.js +187 -0
  177. package/dist/featurePanels.js.map +1 -0
  178. package/dist/filehandleLoader.d.ts +41 -0
  179. package/dist/filehandleLoader.js +99 -0
  180. package/dist/filehandleLoader.js.map +1 -0
  181. package/dist/headlessRenderEnv.d.ts +12 -39
  182. package/dist/headlessRenderEnv.js +18 -12
  183. package/dist/headlessRenderEnv.js.map +1 -1
  184. package/dist/hierarchy.d.ts +5 -8
  185. package/dist/hierarchy.js +5 -79
  186. package/dist/hierarchy.js.map +1 -1
  187. package/dist/index.d.ts +6 -1
  188. package/dist/index.js +6 -1
  189. package/dist/index.js.map +1 -1
  190. package/dist/measureTextCanvas.d.ts +7 -0
  191. package/dist/measureTextCanvas.js +8 -6
  192. package/dist/measureTextCanvas.js.map +1 -1
  193. package/dist/model/treeModel.d.ts +6 -2
  194. package/dist/model/treeModel.js +8 -4
  195. package/dist/model/treeModel.js.map +1 -1
  196. package/dist/model.d.ts +180 -76
  197. package/dist/model.js +358 -618
  198. package/dist/model.js.map +1 -1
  199. package/dist/neighborJoining.js +1 -40
  200. package/dist/neighborJoining.js.map +1 -1
  201. package/dist/renderToSvg.d.ts +2 -7
  202. package/dist/renderToSvg.js +33 -133
  203. package/dist/renderToSvg.js.map +1 -1
  204. package/dist/rerootTree.d.ts +1 -0
  205. package/dist/rerootTree.js +7 -6
  206. package/dist/rerootTree.js.map +1 -1
  207. package/dist/schemeResidueColor.d.ts +12 -0
  208. package/dist/schemeResidueColor.js +33 -0
  209. package/dist/schemeResidueColor.js.map +1 -0
  210. package/dist/similarityColors.d.ts +11 -0
  211. package/dist/similarityColors.js +90 -0
  212. package/dist/similarityColors.js.map +1 -0
  213. package/dist/svgTestUtil.d.ts +74 -32
  214. package/dist/treeDisplay.d.ts +37 -0
  215. package/dist/treeDisplay.js +209 -0
  216. package/dist/treeDisplay.js.map +1 -0
  217. package/dist/types.d.ts +6 -3
  218. package/dist/useWheelScroll.d.ts +3 -1
  219. package/dist/useWheelScroll.js +36 -17
  220. package/dist/useWheelScroll.js.map +1 -1
  221. package/dist/util.d.ts +2 -0
  222. package/dist/util.js +2 -0
  223. package/dist/util.js.map +1 -1
  224. package/dist/version.d.ts +1 -1
  225. package/dist/version.js +1 -1
  226. package/dist/viewerSnapshot.d.ts +169 -0
  227. package/dist/viewerSnapshot.js +100 -0
  228. package/dist/viewerSnapshot.js.map +1 -0
  229. package/dist/wheelZoom.d.ts +8 -3
  230. package/dist/wheelZoom.js +44 -32
  231. package/dist/wheelZoom.js.map +1 -1
  232. package/dist/zoomSnapshot.d.ts +29 -0
  233. package/dist/zoomSnapshot.js +47 -0
  234. package/dist/zoomSnapshot.js.map +1 -0
  235. package/package.json +11 -11
  236. package/src/blosum62.ts +45 -0
  237. package/src/colorSchemes.ts +2 -4
  238. package/src/columnCounts.ts +1 -1
  239. package/src/components/Loading.tsx +25 -14
  240. package/src/components/MSAView.tsx +22 -5
  241. package/src/components/MSAViewer.tsx +275 -251
  242. package/src/components/MetadataTable.tsx +83 -26
  243. package/src/components/ResizeHandles.tsx +9 -2
  244. package/src/components/Track.tsx +15 -1
  245. package/src/components/controls.ts +62 -0
  246. package/src/components/dialogs/AnnotationFileDialog.tsx +16 -21
  247. package/src/components/dialogs/ExportSVGDialog.tsx +8 -19
  248. package/src/components/dialogs/FeatureDialog.tsx +2 -5
  249. package/src/components/dialogs/GapThresholdDialog.tsx +36 -0
  250. package/src/components/{header → dialogs}/GappynessSlider.tsx +3 -10
  251. package/src/components/{header → dialogs}/GoToBox.tsx +36 -10
  252. package/src/components/dialogs/GoToDialog.tsx +30 -0
  253. package/src/components/dialogs/HelpDialog.tsx +79 -0
  254. package/src/components/dialogs/MetadataDialog.tsx +32 -4
  255. package/src/components/{header → dialogs}/goTo.ts +1 -1
  256. package/src/components/dialogs/metadataLabels.ts +54 -0
  257. package/src/components/dialogs/metadataRecord.tsx +61 -0
  258. package/src/components/header/Header.tsx +75 -44
  259. package/src/components/header/HiddenColumnsChip.tsx +39 -0
  260. package/src/components/header/MenuBar.tsx +64 -0
  261. package/src/components/header/ScrollZoomCheckbox.tsx +46 -0
  262. package/src/components/header/ZoomControls.tsx +23 -74
  263. package/src/components/header/colorSchemeMenuItems.ts +121 -0
  264. package/src/components/header/getAnnotationsMenu.ts +52 -56
  265. package/src/components/header/menuBarItems.ts +357 -0
  266. package/src/components/import/ImportForm.tsx +19 -7
  267. package/src/components/import/data/seq2.ts +0 -18
  268. package/src/components/minimap/Minimap.tsx +11 -3
  269. package/src/components/minimap/MinimapSVG.tsx +17 -1
  270. package/src/components/minimap/minimapLayout.ts +27 -0
  271. package/src/components/msa/MSACanvas.tsx +24 -4
  272. package/src/components/msa/MSACanvasBlock.tsx +20 -17
  273. package/src/components/msa/MSAMouseoverCanvas.tsx +2 -2
  274. package/src/components/msa/WheelZoomHint.tsx +56 -0
  275. package/src/components/msa/drawFeatureSpans.ts +160 -79
  276. package/src/components/msa/msaKeys.ts +3 -0
  277. package/src/components/msa/msaRaster.ts +26 -10
  278. package/src/components/msa/renderBoxFeatureCanvasBlock.ts +62 -24
  279. package/src/components/msa/renderHighlights.ts +5 -6
  280. package/src/components/msa/renderMSABlock.ts +51 -15
  281. package/src/components/msa/renderMSAMouseover.ts +5 -1
  282. package/src/components/msa/tileColor.ts +7 -0
  283. package/src/components/renderCtx.ts +0 -1
  284. package/src/components/rowpanels/RowPanelBlock.tsx +2 -2
  285. package/src/components/rowpanels/renderFeaturePanel.ts +26 -7
  286. package/src/components/settings/MenuItemsForm.tsx +225 -0
  287. package/src/components/settings/SettingsPanel.tsx +156 -0
  288. package/src/components/settings/TrackList.tsx +119 -0
  289. package/src/components/tracks/TrackBlocks.tsx +2 -2
  290. package/src/components/tracks/drawTracks.ts +5 -5
  291. package/src/components/tree/CladeLabels.tsx +30 -54
  292. package/src/components/tree/TreeCanvasBlock.tsx +2 -2
  293. package/src/components/tree/TreeMenu.tsx +4 -6
  294. package/src/components/tree/TreeOverview.tsx +2 -2
  295. package/src/components/tree/cladeBrackets.ts +50 -3
  296. package/src/components/tree/renderTreeCanvas.ts +2 -13
  297. package/src/components/tree/renderTreeOverview.ts +7 -14
  298. package/src/constants.ts +9 -1
  299. package/src/darkReader.ts +57 -0
  300. package/src/element.ts +37 -64
  301. package/src/exportLayout.ts +134 -0
  302. package/src/featurePanels.ts +269 -0
  303. package/src/filehandleLoader.ts +136 -0
  304. package/src/headlessRenderEnv.ts +24 -11
  305. package/src/hierarchy.ts +5 -88
  306. package/src/index.ts +9 -5
  307. package/src/measureTextCanvas.ts +8 -6
  308. package/src/model/treeModel.ts +9 -3
  309. package/src/model.ts +452 -807
  310. package/src/neighborJoining.ts +1 -42
  311. package/src/renderToSvg.tsx +56 -207
  312. package/src/rerootTree.ts +7 -6
  313. package/src/schemeResidueColor.ts +37 -0
  314. package/src/similarityColors.ts +112 -0
  315. package/src/treeDisplay.ts +278 -0
  316. package/src/types.ts +6 -4
  317. package/src/useWheelScroll.ts +43 -22
  318. package/src/util.ts +3 -0
  319. package/src/version.ts +1 -1
  320. package/src/viewerSnapshot.ts +147 -0
  321. package/src/wheelZoom.ts +55 -33
  322. package/src/zoomSnapshot.ts +80 -0
  323. package/dist/components/header/ColorSchemeMenu.d.ts +0 -12
  324. package/dist/components/header/ColorSchemeMenu.js +0 -37
  325. package/dist/components/header/ColorSchemeMenu.js.map +0 -1
  326. package/dist/components/header/FileMenu.js +0 -66
  327. package/dist/components/header/FileMenu.js.map +0 -1
  328. package/dist/components/header/GappynessSlider.js.map +0 -1
  329. package/dist/components/header/GoToBox.js +0 -31
  330. package/dist/components/header/GoToBox.js.map +0 -1
  331. package/dist/components/header/MSASettingsMenu.d.ts +0 -6
  332. package/dist/components/header/MSASettingsMenu.js +0 -11
  333. package/dist/components/header/MSASettingsMenu.js.map +0 -1
  334. package/dist/components/header/ScrollZoomIcons.d.ts +0 -6
  335. package/dist/components/header/ScrollZoomIcons.js +0 -35
  336. package/dist/components/header/ScrollZoomIcons.js.map +0 -1
  337. package/dist/components/header/TreeSettingsMenu.js +0 -42
  338. package/dist/components/header/TreeSettingsMenu.js.map +0 -1
  339. package/dist/components/header/ZoomMenu.js +0 -37
  340. package/dist/components/header/ZoomMenu.js.map +0 -1
  341. package/dist/components/header/goTo.d.ts +0 -20
  342. package/dist/components/header/goTo.js.map +0 -1
  343. package/dist/components/header/settingsMenuItems.d.ts +0 -4
  344. package/dist/components/header/settingsMenuItems.js +0 -127
  345. package/dist/components/header/settingsMenuItems.js.map +0 -1
  346. package/src/components/header/ColorSchemeMenu.tsx +0 -56
  347. package/src/components/header/FileMenu.tsx +0 -81
  348. package/src/components/header/MSASettingsMenu.tsx +0 -26
  349. package/src/components/header/ScrollZoomIcons.tsx +0 -52
  350. package/src/components/header/TreeSettingsMenu.tsx +0 -60
  351. package/src/components/header/ZoomMenu.tsx +0 -48
  352. package/src/components/header/settingsMenuItems.ts +0 -157
@@ -1,48 +1,7 @@
1
1
  // Neighbor Joining tree construction using BLOSUM62 distances
2
2
  // Based on Saitou & Nei (1987) "The neighbor-joining method"
3
3
 
4
- // prettier-ignore
5
- const BLOSUM62_KEYS = 'ARNDCQEGHILKMFPSTWYVBZX*'
6
- // prettier-ignore
7
- const BLOSUM62_DATA = [
8
- [ 4,-1,-2,-2, 0,-1,-1, 0,-2,-1,-1,-1,-1,-2,-1, 1, 0,-3,-2, 0,-2,-1, 0,-4],
9
- [-1, 5, 0,-2,-3, 1, 0,-2, 0,-3,-2, 2,-1,-3,-2,-1,-1,-3,-2,-3,-1, 0,-1,-4],
10
- [-2, 0, 6, 1,-3, 0, 0, 0, 1,-3,-3, 0,-2,-3,-2, 1, 0,-4,-2,-3, 3, 0,-1,-4],
11
- [-2,-2, 1, 6,-3, 0, 2,-1,-1,-3,-4,-1,-3,-3,-1, 0,-1,-4,-3,-3, 4, 1,-1,-4],
12
- [ 0,-3,-3,-3, 9,-3,-4,-3,-3,-1,-1,-3,-1,-2,-3,-1,-1,-2,-2,-1,-3,-3,-2,-4],
13
- [-1, 1, 0, 0,-3, 5, 2,-2, 0,-3,-2, 1, 0,-3,-1, 0,-1,-2,-1,-2, 0, 3,-1,-4],
14
- [-1, 0, 0, 2,-4, 2, 5,-2, 0,-3,-3, 1,-2,-3,-1, 0,-1,-3,-2,-2, 1, 4,-1,-4],
15
- [ 0,-2, 0,-1,-3,-2,-2, 6,-2,-4,-4,-2,-3,-3,-2, 0,-2,-2,-3,-3,-1,-2,-1,-4],
16
- [-2, 0, 1,-1,-3, 0, 0,-2, 8,-3,-3,-1,-2,-1,-2,-1,-2,-2, 2,-3, 0, 0,-1,-4],
17
- [-1,-3,-3,-3,-1,-3,-3,-4,-3, 4, 2,-3, 1, 0,-3,-2,-1,-3,-1, 3,-3,-3,-1,-4],
18
- [-1,-2,-3,-4,-1,-2,-3,-4,-3, 2, 4,-2, 2, 0,-3,-2,-1,-2,-1, 1,-4,-3,-1,-4],
19
- [-1, 2, 0,-1,-3, 1, 1,-2,-1,-3,-2, 5,-1,-3,-1, 0,-1,-3,-2,-2, 0, 1,-1,-4],
20
- [-1,-1,-2,-3,-1, 0,-2,-3,-2, 1, 2,-1, 5, 0,-2,-1,-1,-1,-1, 1,-3,-1,-1,-4],
21
- [-2,-3,-3,-3,-2,-3,-3,-3,-1, 0, 0,-3, 0, 6,-4,-2,-2, 1, 3,-1,-3,-3,-1,-4],
22
- [-1,-2,-2,-1,-3,-1,-1,-2,-2,-3,-3,-1,-2,-4, 7,-1,-1,-4,-3,-2,-2,-1,-2,-4],
23
- [ 1,-1, 1, 0,-1, 0, 0, 0,-1,-2,-2, 0,-1,-2,-1, 4, 1,-3,-2,-2, 0, 0, 0,-4],
24
- [ 0,-1, 0,-1,-1,-1,-1,-2,-2,-1,-1,-1,-1,-2,-1, 1, 5,-2,-2, 0,-1,-1, 0,-4],
25
- [-3,-3,-4,-4,-2,-2,-3,-2,-2,-3,-2,-3,-1, 1,-4,-3,-2,11, 2,-3,-4,-3,-2,-4],
26
- [-2,-2,-2,-3,-2,-1,-2,-3, 2,-1,-1,-2,-1, 3,-3,-2,-2, 2, 7,-1,-3,-2,-1,-4],
27
- [ 0,-3,-3,-3,-1,-2,-2,-3,-3, 3, 1,-2, 1,-1,-2,-2, 0,-3,-1, 4,-3,-2,-1,-4],
28
- [-2,-1, 3, 4,-3, 0, 1,-1, 0,-3,-4, 0,-3,-3,-2, 0,-1,-4,-3,-3, 4, 1,-1,-4],
29
- [-1, 0, 0, 1,-3, 3, 4,-2, 0,-3,-3, 1,-1,-3,-1, 0,-1,-3,-2,-2, 1, 4,-1,-4],
30
- [ 0,-1,-1,-1,-2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-2, 0, 0,-2,-1,-1,-1,-1,-1,-4],
31
- [-4,-4,-4,-4,-4,-4,-4,-4,-4,-4,-4,-4,-4,-4,-4,-4,-4,-4,-4,-4,-4,-4,-4, 1],
32
- ]
33
-
34
- const NUM_SYMBOLS = BLOSUM62_KEYS.length
35
- const BLOSUM62 = Int8Array.from(BLOSUM62_DATA.flat())
36
-
37
- // charCode -> row/column in BLOSUM62, -1 for anything the matrix does not name.
38
- // On a 120x800 alignment a Map keyed by upper-cased character took 2.6s to
39
- // build a tree, against 0.34s for this table.
40
- const symbolOfCode = new Int8Array(128).fill(-1)
41
- for (let i = 0; i < NUM_SYMBOLS; i++) {
42
- const key = BLOSUM62_KEYS[i]!
43
- symbolOfCode[key.charCodeAt(0)] = i
44
- symbolOfCode[key.toLowerCase().charCodeAt(0)] = i
45
- }
4
+ import { BLOSUM62, NUM_SYMBOLS, symbolOfCode } from './blosum62.ts'
46
5
 
47
6
  // gap, or past the end of a row shorter than the alignment
48
7
  const GAP = -2
@@ -6,8 +6,7 @@ import { when } from 'mobx'
6
6
  import { visibleRowRange } from './components/getVisibleLeaves.ts'
7
7
  import MinimapSVG from './components/minimap/MinimapSVG.tsx'
8
8
  import { legendRows } from './components/msa/legendRows.ts'
9
- import { rasterImageHref, rasterSupported } from './components/msa/msaRaster.ts'
10
- import { renderBoxFeatureCanvasBlock } from './components/msa/renderBoxFeatureCanvasBlock.ts'
9
+ import { rasterApplies, rasterImageHref } from './components/msa/msaRaster.ts'
11
10
  import { renderPersistentHighlights } from './components/msa/renderHighlights.ts'
12
11
  import { renderMSABlock } from './components/msa/renderMSABlock.ts'
13
12
  import { visibleColRange } from './components/msa/visibleColRange.ts'
@@ -15,141 +14,32 @@ import { headerRunsAcross } from './components/rowpanels/headerLayout.ts'
15
14
  import { renderRowPanels } from './components/rowpanels/renderRowPanel.ts'
16
15
  import { renderAllTracks } from './components/tracks/drawTracks.ts'
17
16
  import ScaleBarSVG from './components/tree/ScaleBarSVG.tsx'
18
- import {
19
- bracketGap,
20
- cladeHeight,
21
- cladeInGutter,
22
- cladeLabelHorizontal,
23
- cladeLabelOffset,
24
- } from './components/tree/cladeBrackets.ts'
17
+ import { cladeLabelBoxes } from './components/tree/cladeBrackets.ts'
18
+ import { ellipsize } from './components/tree/ellipsize.ts'
25
19
  import { renderTreeCanvas } from './components/tree/renderTreeCanvas.ts'
26
20
  import { renderTreeOverview } from './components/tree/renderTreeOverview.ts'
27
21
  import { treeScaleBarHeight } from './constants.ts'
22
+ import {
23
+ LEGEND_CHAR_W,
24
+ LEGEND_FONT,
25
+ LEGEND_PAD,
26
+ LEGEND_ROW_H,
27
+ LEGEND_SWATCH,
28
+ LEGEND_TEXT_X,
29
+ exportPageSize,
30
+ getLayout,
31
+ legendHeight,
32
+ } from './exportLayout.ts'
28
33
  import { measureTextCanvas } from './measureTextCanvas.ts'
29
34
  import { renderToStaticMarkup, svgSafeColors } from './renderToStaticMarkup.ts'
30
35
  import { outlineColor } from './util.ts'
31
36
 
32
- import type { LegendRow } from './components/msa/legendRows.ts'
37
+ import type { ExportSvgOptions, Layout } from './exportLayout.ts'
33
38
  import type { MsaViewModel } from './model.ts'
34
39
  import type { Context as ContextType } from '@jbrowse/svgcanvas'
35
40
  import type { Theme } from '@mui/material'
36
41
 
37
- export interface ExportSvgOptions {
38
- theme: Theme
39
- includeMinimap?: boolean
40
- includeTracks?: boolean
41
- exportType: 'entire' | 'viewport'
42
- }
43
-
44
- // legend geometry, shared by the width calculation and the renderer so the
45
- // reserved column on the right exactly fits the drawn legends
46
- const LEGEND_PAD = 8
47
- const LEGEND_ROW_H = 16
48
- const LEGEND_SWATCH = 12
49
- const LEGEND_FONT = 12
50
- const LEGEND_CHAR_W = 7
51
-
52
- // the width of a character as a fraction of the font size, close enough for a
53
- // sans-serif average
54
- const CHAR_WIDTH = 0.6
55
-
56
- const LEGEND_MAX_W = 360
57
- const LEGEND_TEXT_X = LEGEND_PAD + LEGEND_SWATCH + 6
58
-
59
- function getLegendWidth(rows: LegendRow[]) {
60
- const maxLen = rows.reduce((a, r) => Math.max(a, r.label.length), 0)
61
- const w = LEGEND_TEXT_X + LEGEND_PAD + maxLen * LEGEND_CHAR_W
62
- return Math.min(LEGEND_MAX_W, Math.max(120, w))
63
- }
64
-
65
- function legendHeight(rows: LegendRow[]) {
66
- return LEGEND_PAD * 2 + rows.length * LEGEND_ROW_H
67
- }
68
-
69
- // resolved sizes/offsets (in svg user units) for the chosen export, shared by
70
- // every layer; 'entire' renders the whole alignment unscrolled, 'viewport'
71
- // mirrors the live scroll position
72
- interface Layout {
73
- width: number
74
- msaAreaWidth: number
75
- height: number
76
- contentHeight: number
77
- trackHeight: number
78
- // the band the minimap, the row panel headers and the tree overview stacked
79
- // on the scale bar share across the top
80
- topHeight: number
81
- offsetX: number
82
- offsetY: number
83
- includeMinimap: boolean
84
- legendWidth: number
85
- }
86
-
87
- function getLayout(model: MsaViewModel, opts: ExportSvgOptions): Layout {
88
- const {
89
- scrollX,
90
- scrollY,
91
- totalWidth,
92
- totalHeight,
93
- effectiveTreeAreaWidth,
94
- rowPanelsWidth,
95
- rowPanelsHeaderHeight,
96
- totalTrackAreaHeight,
97
- minimapHeight,
98
- msaAreaHeight,
99
- msaCanvasWidth,
100
- showHorizontalScrollbar,
101
- treeOverviewHeight,
102
- treeScaleBar,
103
- } = model
104
- const trackHeight = opts.includeTracks ? totalTrackAreaHeight : 0
105
- // the minimap reflects the live viewport scroll position, so it's only
106
- // meaningful for a viewport export, never for the entire alignment -- and
107
- // only when the live view shows one at all, since a minimap over an alignment
108
- // that already fits marks a viewport wider than the bar
109
- const includeMinimap =
110
- opts.exportType === 'viewport' &&
111
- !!opts.includeMinimap &&
112
- showHorizontalScrollbar
113
- const rows = legendRows(model.legends)
114
- const legendWidth = rows.length > 0 ? getLegendWidth(rows) : 0
115
- const topHeight = Math.max(
116
- includeMinimap ? minimapHeight : 0,
117
- rowPanelsHeaderHeight,
118
- treeOverviewHeight + (treeScaleBar ? treeScaleBarHeight : 0),
119
- )
120
- const gutterWidth = effectiveTreeAreaWidth + rowPanelsWidth
121
-
122
- // width stays content-only; the legend occupies an extra column added at the
123
- // svg root in MsaSvg. The viewport export takes the alignment canvas's own
124
- // size rather than the widget's -- the widget box also covers the header, the
125
- // resize handle and the scrollbars, and rendering that box exports rows and
126
- // columns the scrollbars hide on screen
127
- return opts.exportType === 'entire'
128
- ? {
129
- width: totalWidth + gutterWidth,
130
- msaAreaWidth: totalWidth,
131
- height: totalHeight + trackHeight + topHeight,
132
- contentHeight: totalHeight,
133
- trackHeight,
134
- topHeight,
135
- offsetX: 0,
136
- offsetY: 0,
137
- includeMinimap,
138
- legendWidth,
139
- }
140
- : {
141
- width: msaCanvasWidth + gutterWidth,
142
- msaAreaWidth: msaCanvasWidth,
143
- height: msaAreaHeight + topHeight + trackHeight,
144
- contentHeight: msaAreaHeight,
145
- trackHeight,
146
- topHeight,
147
- offsetX: -scrollX,
148
- offsetY: -scrollY,
149
- includeMinimap,
150
- legendWidth,
151
- }
152
- }
42
+ export type { ExportSvgOptions } from './exportLayout.ts'
153
43
 
154
44
  export async function renderToSvg(model: MsaViewModel, opts: ExportSvgOptions) {
155
45
  // dataInitialized stays false while the model holds a load error, so waiting
@@ -173,12 +63,13 @@ export async function renderToSvg(model: MsaViewModel, opts: ExportSvgOptions) {
173
63
  layers={layers}
174
64
  />,
175
65
  )
66
+ // innerHTML serializes as HTML, and an XML parser rejects its &nbsp;
176
67
  return svgSafeColors(
177
68
  markup.replaceAll(/<g data-layer="([^"]+)"><\/g>/g, (match, id: string) => {
178
69
  const ctx = layers.get(id)
179
70
  return ctx ? `<g id="${id}">${ctx.getSvg().innerHTML}</g>` : match
180
71
  }),
181
- )
72
+ ).replaceAll('&nbsp;', '&#160;')
182
73
  }
183
74
 
184
75
  function MsaSvg({
@@ -196,17 +87,8 @@ function MsaSvg({
196
87
  }) {
197
88
  const { width, trackHeight, includeMinimap, legendWidth, topHeight } = layout
198
89
  const { effectiveTreeAreaWidth, rowPanelsWidth } = model
199
- const totalWidth = width + legendWidth
90
+ const { width: totalWidth, height } = exportPageSize(model, layout)
200
91
  const legendTop = topHeight
201
- // a short alignment with many legend entries has a key taller than its rows,
202
- // and the page has to hold the whole key
203
- const height =
204
- legendWidth > 0
205
- ? Math.max(
206
- layout.height,
207
- legendTop + legendHeight(legendRows(model.legends)),
208
- )
209
- : layout.height
210
92
  const props = { Context, model, theme, layout, layers }
211
93
 
212
94
  const body = (
@@ -227,6 +109,9 @@ function MsaSvg({
227
109
  xmlns="http://www.w3.org/2000/svg"
228
110
  xmlnsXlink="http://www.w3.org/1999/xlink"
229
111
  viewBox={`0 0 ${totalWidth} ${height}`}
112
+ // the canvas layers name sans-serif on each glyph; the legend, labels
113
+ // and headers inherit it here instead of an editor's default serif
114
+ fontFamily="sans-serif"
230
115
  >
231
116
  {/* every layer below draws in the theme's colors, so the page has to be
232
117
  the theme's background too -- a hardcoded white one turns a dark-theme
@@ -359,7 +244,6 @@ function RowPanelHeadersSVG({
359
244
  const center = effectiveTreeAreaWidth + panel.offsetX + panel.width / 2
360
245
  const x = across ? center : center + size / 3
361
246
  const room = across ? panel.width : y - LEGEND_PAD
362
- const maxChars = Math.floor(room / (size * CHAR_WIDTH))
363
247
  return size < 5 ? null : (
364
248
  <text
365
249
  key={panel.id}
@@ -371,9 +255,10 @@ function RowPanelHeadersSVG({
371
255
  fontSize={size}
372
256
  fill={theme.palette.text.primary}
373
257
  >
374
- {panel.header.length > maxChars
375
- ? `${panel.header.slice(0, Math.max(1, maxChars - 1))}…`
376
- : panel.header}
258
+ {
259
+ ellipsize(t => measureTextCanvas(t, size), panel.header, room)
260
+ .text
261
+ }
377
262
  </text>
378
263
  )
379
264
  })}
@@ -381,21 +266,11 @@ function RowPanelHeadersSVG({
381
266
  )
382
267
  }
383
268
 
384
- // A label past the room it has, cut to what fits with an ellipsis. The measure
385
- // is the one cladeGutterWidth sizes the gutter with, so a label the gutter
386
- // holds keeps every character.
387
- function clipCladeLabel(label: string, room: number, fontSize: number) {
388
- const width = measureTextCanvas(label, fontSize)
389
- if (width <= room) {
390
- return label
391
- }
392
- const fits = Math.floor((label.length * room) / width)
393
- return `${label.slice(0, Math.max(1, fits - 1))}…`
394
- }
395
-
396
269
  // Each bracket's label in the gutter beside its bar, where the live view puts
397
270
  // it (see CladeLabels). The canvas layer draws the bar; a label longer than the
398
271
  // gutter or the clade is clipped, since text that overruns runs off the figure.
272
+ // cladeGutterWidth sizes the gutter with the same measureTextCanvas, so a label
273
+ // the gutter holds keeps every character.
399
274
  function CladeLabelsSVG({
400
275
  model,
401
276
  theme,
@@ -405,43 +280,32 @@ function CladeLabelsSVG({
405
280
  theme: Theme
406
281
  offsetY: number
407
282
  }) {
408
- const {
409
- resolvedClades,
410
- cladeGutterWidth,
411
- rowHeight,
412
- effectiveTreeAreaWidth,
413
- fontSize,
414
- } = model
415
- if (cladeGutterWidth === 0) {
416
- return null
417
- }
418
- const gutterLeft = effectiveTreeAreaWidth - cladeGutterWidth
419
- return (
283
+ const { fontSize } = model
284
+ const boxes = cladeLabelBoxes(model)
285
+ return boxes.length === 0 ? null : (
420
286
  <g id="clade-labels">
421
- {resolvedClades.map((clade, index) => {
422
- const { label, markColor, rows } = clade
423
- if (!label || !cladeInGutter(clade)) {
424
- return null
425
- }
426
- const left = gutterLeft + cladeLabelOffset(clade)
427
- const top = rows[0] * rowHeight - offsetY
428
- const height = cladeHeight(clade, rowHeight)
429
- const horizontal = cladeLabelHorizontal(clade, rowHeight, fontSize)
430
- const room = horizontal
431
- ? effectiveTreeAreaWidth - left - bracketGap
432
- : height
433
- const x = horizontal ? left : left + fontSize
434
- const y = horizontal ? top + height / 2 + fontSize / 3 : top + height
287
+ {boxes.map(box => {
288
+ const top = box.top - offsetY
289
+ const x = box.horizontal ? box.left : box.left + fontSize
290
+ const y = box.horizontal
291
+ ? top + box.height / 2 + fontSize / 3
292
+ : top + box.height
435
293
  return (
436
294
  <text
437
- key={`${rows[0]}-${rows[1]}-${index}`}
295
+ key={box.key}
438
296
  x={x}
439
297
  y={y}
440
- transform={horizontal ? undefined : `rotate(-90 ${x} ${y})`}
298
+ transform={box.horizontal ? undefined : `rotate(-90 ${x} ${y})`}
441
299
  fontSize={fontSize}
442
- fill={markColor ?? theme.palette.text.primary}
300
+ fill={box.color ?? theme.palette.text.primary}
443
301
  >
444
- {clipCladeLabel(label, room, fontSize)}
302
+ {
303
+ ellipsize(
304
+ t => measureTextCanvas(t, fontSize),
305
+ box.label,
306
+ box.horizontal ? box.width : box.height,
307
+ ).text
308
+ }
445
309
  </text>
446
310
  )
447
311
  })}
@@ -464,9 +328,7 @@ function LegendSVG({
464
328
  const boxHeight = legendHeight(rows)
465
329
  // the column is capped at LEGEND_MAX_W, so a name too long for it has to be
466
330
  // clipped here -- text that overruns the reserved width runs off the figure
467
- const maxChars = Math.floor(
468
- (width - LEGEND_TEXT_X - LEGEND_PAD) / LEGEND_CHAR_W,
469
- )
331
+ const room = width - LEGEND_TEXT_X - LEGEND_PAD
470
332
  return (
471
333
  <g>
472
334
  <rect
@@ -499,9 +361,7 @@ function LegendSVG({
499
361
  fontWeight={row.color ? undefined : 'bold'}
500
362
  fill={theme.palette.text.primary}
501
363
  >
502
- {row.label.length > maxChars
503
- ? `${row.label.slice(0, Math.max(1, maxChars - 1))}…`
504
- : row.label}
364
+ {ellipsize(t => t.length * LEGEND_CHAR_W, row.label, room).text}
505
365
  </text>
506
366
  </g>
507
367
  )
@@ -551,18 +411,6 @@ function CoreRendering({ model, theme, layout, Context, layers }: LayerProps) {
551
411
  const raster = rasterBackground({ model, theme, layout })
552
412
 
553
413
  const msaCtx = new Context(msaAreaWidth, contentHeight)
554
- renderBoxFeatureCanvasBlock({
555
- model,
556
- theme,
557
- ctx: msaCtx,
558
- offsetX,
559
- offsetY,
560
- // the overlay culls bands to this width; without it the export falls back
561
- // to the on-screen 500px block size and drops every band past it
562
- blockSizeXOverride: msaAreaWidth,
563
- blockSizeYOverride: contentHeight,
564
- highResScaleFactorOverride: 1,
565
- })
566
414
  renderMSABlock({
567
415
  model,
568
416
  theme,
@@ -573,6 +421,7 @@ function CoreRendering({ model, theme, layout, Context, layers }: LayerProps) {
573
421
  blockSizeYOverride: contentHeight,
574
422
  highResScaleFactorOverride: 1,
575
423
  rasterTiles: !!raster,
424
+ domains: true,
576
425
  })
577
426
  msaCtx.resetTransform()
578
427
  renderPersistentHighlights({
@@ -622,9 +471,8 @@ function CoreRendering({ model, theme, layout, Context, layers }: LayerProps) {
622
471
  }
623
472
 
624
473
  /**
625
- * The alignment background as a single <image>, under the live canvas's
626
- * conditions: no domain overlay boxes, background coloring on, and a canvas
627
- * that reads back.
474
+ * The alignment background as a single <image>, wherever the live canvas
475
+ * draws the raster.
628
476
  *
629
477
  * The vector path's <rect> per cell exhausts the heap on a real alignment. The
630
478
  * image is one pixel per cell, and image-rendering keeps the upscaled cell
@@ -640,8 +488,8 @@ function rasterBackground({
640
488
  layout: Layout
641
489
  }) {
642
490
  const { contentHeight, offsetX, offsetY, msaAreaWidth } = layout
643
- const { colWidth, rowHeight, actuallyShowDomains, bgColor } = model
644
- if (actuallyShowDomains || !bgColor || !rasterSupported()) {
491
+ const { colWidth, rowHeight } = model
492
+ if (!rasterApplies(model)) {
645
493
  return undefined
646
494
  }
647
495
  const { xStart, xEnd } = visibleColRange({
@@ -728,7 +576,8 @@ function TrackLabelsSVG({
728
576
  const top = turnedOnTracks
729
577
  .slice(0, i)
730
578
  .reduce((acc, t) => acc + t.model.height, 0)
731
- return { id, name, size, baseline: top + height / 2 + size / 3 }
579
+ // pinned to the top of the track, as the live view's flex row pins it
580
+ return { id, name, size, baseline: top + size * 0.8 }
732
581
  })
733
582
  return (
734
583
  <>
package/src/rerootTree.ts CHANGED
@@ -13,19 +13,18 @@ export interface TreeNode {
13
13
  children?: TreeNode[]
14
14
  }
15
15
 
16
- function preorder<N extends TreeNode>(root: N) {
16
+ export function preorder<N extends TreeNode>(root: N, parents?: Map<N, N>) {
17
17
  const order: N[] = []
18
- const parents = new Map<N, N>()
19
18
  const stack = [root]
20
19
  while (stack.length > 0) {
21
20
  const node = stack.pop()!
22
21
  order.push(node)
23
22
  for (const child of (node.children ?? []) as N[]) {
24
- parents.set(child, node)
23
+ parents?.set(child, node)
25
24
  stack.push(child)
26
25
  }
27
26
  }
28
- return { order, parents }
27
+ return order
29
28
  }
30
29
 
31
30
  function hasLengths(order: TreeNode[]) {
@@ -56,7 +55,8 @@ export function rerootTree<N extends TreeNode>(
56
55
  target: N,
57
56
  offset?: number,
58
57
  ): TreeNode {
59
- const { order, parents } = preorder(root)
58
+ const parents = new Map<N, N>()
59
+ const order = preorder(root, parents)
60
60
  if (!parents.has(target)) {
61
61
  return root
62
62
  }
@@ -112,7 +112,8 @@ export function rerootTree<N extends TreeNode>(
112
112
  * counts branches.
113
113
  */
114
114
  export function midpointRoot<N extends TreeNode>(root: N): TreeNode {
115
- const { order, parents } = preorder(root)
115
+ const parents = new Map<N, N>()
116
+ const order = preorder(root, parents)
116
117
  const tips = order.filter(n => !n.children?.length)
117
118
  if (tips.length < 2) {
118
119
  return root
@@ -0,0 +1,37 @@
1
+ import { colord } from 'colord'
2
+
3
+ import { tileColorFn } from './components/msa/tileColor.ts'
4
+
5
+ import type { MsaViewModel } from './model.ts'
6
+
7
+ /**
8
+ * A reader for the color the active scheme gives a row's residue, as hex, for a
9
+ * host that paints another view, such as a structure, with the alignment's own
10
+ * colors. `seqPos` is 1-based, like `structureResidue`. The reader returns
11
+ * undefined where the scheme leaves the residue uncolored, the row lacks it, or
12
+ * its column is hidden.
13
+ *
14
+ * Build it inside a reaction: a new scheme, a hidden gap column or a collapsed
15
+ * clade then re-runs the caller.
16
+ */
17
+ export function schemeResidueColor(model: MsaViewModel) {
18
+ const colorAt = tileColorFn(model)
19
+ const { columns } = model
20
+ const hexes = new Map<string, string | undefined>()
21
+ const toHex = (color: string) => {
22
+ if (!hexes.has(color)) {
23
+ const parsed = colord(color)
24
+ hexes.set(color, parsed.isValid() ? parsed.toHex() : undefined)
25
+ }
26
+ return hexes.get(color)
27
+ }
28
+ return (rowName: string, seqPos: number) => {
29
+ const col = model.seqPosToVisibleCol(rowName, seqPos - 1)
30
+ const letter = col === undefined ? undefined : columns.get(rowName)?.[col]
31
+ const color =
32
+ col === undefined || letter === undefined
33
+ ? undefined
34
+ : colorAt(col, letter)
35
+ return color === undefined ? undefined : toHex(color)
36
+ }
37
+ }
@@ -0,0 +1,112 @@
1
+ // Similarity shading, after biotite's plot_alignment_similarity_based and
2
+ // Jalview's BLOSUM62 score scheme. Each residue takes a shade of one hue by how
3
+ // similar it is, on average, to the other residues of its column.
4
+
5
+ import { BLOSUM62, NUM_SYMBOLS, symbolOfCode } from './blosum62.ts'
6
+ import { letterOfResidueSlot, numResidueSlots } from './columnCounts.ts'
7
+
8
+ import type { ColumnCounts } from './columnCounts.ts'
9
+
10
+ const AMINO_ACIDS = 20
11
+ const HUE = 240
12
+ const SATURATION = 50
13
+ const LIGHTEST = 96
14
+ const DARKEST = 40
15
+
16
+ // BLOSUM62 scaled per row to 0..1 over the twenty amino acids, so a residue's
17
+ // own score is 1 and its worst substitution 0. Not symmetric, like biotite's.
18
+ const normalized = new Float32Array(NUM_SYMBOLS * NUM_SYMBOLS)
19
+ for (let a = 0; a < NUM_SYMBOLS; a++) {
20
+ let min = Infinity
21
+ let max = -Infinity
22
+ for (let b = 0; b < AMINO_ACIDS; b++) {
23
+ const score = BLOSUM62[a * NUM_SYMBOLS + b]!
24
+ min = Math.min(min, score)
25
+ max = Math.max(max, score)
26
+ }
27
+ for (let b = 0; b < NUM_SYMBOLS; b++) {
28
+ const scaled = (BLOSUM62[a * NUM_SYMBOLS + b]! - min) / (max - min)
29
+ normalized[a * NUM_SYMBOLS + b] = Math.min(Math.max(scaled, 0), 1)
30
+ }
31
+ }
32
+
33
+ // residue slot -> row of the matrix, -1 for a letter the matrix does not name
34
+ const symbolOfSlot = Int8Array.from({ length: numResidueSlots }, (_, slot) => {
35
+ const letter = letterOfResidueSlot(slot)
36
+ return letter.length === 1 ? symbolOfCode[letter.charCodeAt(0)]! : -1
37
+ })
38
+
39
+ function shade(similarity: number) {
40
+ const lightness = Math.round(LIGHTEST - (LIGHTEST - DARKEST) * similarity)
41
+ return `hsl(${HUE}, ${SATURATION}%, ${lightness}%)`
42
+ }
43
+
44
+ interface Measure {
45
+ /** whether a residue counts toward the means at all */
46
+ counted: (slot: number) => boolean
47
+ /** whether a residue takes a shade, given it is counted */
48
+ shaded: (slot: number) => boolean
49
+ /** similarity of residue `a` to residue `b`, 0..1 */
50
+ score: (a: number, b: number) => number
51
+ }
52
+
53
+ /**
54
+ * Shades each residue of one column by its mean similarity to the column's
55
+ * other residues, gaps left out of the mean. A column of fewer than two counted
56
+ * residues has nothing to compare and colors nothing.
57
+ */
58
+ function shadeColumn(counts: ColumnCounts, col: number, measure: Measure) {
59
+ const { counted, shaded, score } = measure
60
+ const present: [slot: number, count: number][] = []
61
+ let total = 0
62
+ counts.forEachResidue(col, (slot, count) => {
63
+ if (counted(slot)) {
64
+ present.push([slot, count])
65
+ total += count
66
+ }
67
+ })
68
+
69
+ const colors: Record<string, string> = {}
70
+ if (total < 2) {
71
+ return colors
72
+ }
73
+ for (const [a] of present) {
74
+ if (shaded(a)) {
75
+ let sum = -score(a, a)
76
+ for (const [b, count] of present) {
77
+ sum += count * score(a, b)
78
+ }
79
+ colors[letterOfResidueSlot(a)] = shade(sum / (total - 1))
80
+ }
81
+ }
82
+ return colors
83
+ }
84
+
85
+ const blosum62: Measure = {
86
+ counted: slot => symbolOfSlot[slot]! >= 0,
87
+ shaded: slot => symbolOfSlot[slot]! < AMINO_ACIDS,
88
+ score: (a, b) =>
89
+ normalized[symbolOfSlot[a]! * NUM_SYMBOLS + symbolOfSlot[b]!]!,
90
+ }
91
+
92
+ const identity: Measure = {
93
+ counted: () => true,
94
+ shaded: () => true,
95
+ score: (a, b) => (a === b ? 1 : 0),
96
+ }
97
+
98
+ /**
99
+ * The amino acids of a column shaded by BLOSUM62 similarity. Letters the matrix
100
+ * does not name, such as U, O and J, count for nothing and take no shade.
101
+ */
102
+ export function blosum62ColumnColors(counts: ColumnCounts, col: number) {
103
+ return shadeColumn(counts, col, blosum62)
104
+ }
105
+
106
+ /**
107
+ * Nucleotides and anything else without a substitution matrix: a residue's
108
+ * similarity is the fraction of the rest of its column that equals it.
109
+ */
110
+ export function identityColumnColors(counts: ColumnCounts, col: number) {
111
+ return shadeColumn(counts, col, identity)
112
+ }