react-msaview 8.1.0 → 8.3.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 (195) hide show
  1. package/bundle/index.js +104 -117
  2. package/bundle/index.js.map +4 -4
  3. package/dist/columnCounts.d.ts +4 -3
  4. package/dist/columnCounts.js +11 -6
  5. package/dist/columnCounts.js.map +1 -1
  6. package/dist/components/CopyButton.d.ts +1 -1
  7. package/dist/components/CopyButton.js +1 -1
  8. package/dist/components/CopyButton.js.map +1 -1
  9. package/dist/components/DragHandle.js +14 -2
  10. package/dist/components/DragHandle.js.map +1 -1
  11. package/dist/components/Loading.js +10 -11
  12. package/dist/components/Loading.js.map +1 -1
  13. package/dist/components/MSAViewer.d.ts +12 -1
  14. package/dist/components/MSAViewer.js +12 -1
  15. package/dist/components/MSAViewer.js.map +1 -1
  16. package/dist/components/SequenceTextArea.d.ts +11 -0
  17. package/dist/components/SequenceTextArea.js +40 -8
  18. package/dist/components/SequenceTextArea.js.map +1 -1
  19. package/dist/components/StatusMessage.d.ts +12 -0
  20. package/dist/components/StatusMessage.js +43 -0
  21. package/dist/components/StatusMessage.js.map +1 -0
  22. package/dist/components/Track.js +3 -3
  23. package/dist/components/Track.js.map +1 -1
  24. package/dist/components/VerticalScrollbar.js +6 -1
  25. package/dist/components/VerticalScrollbar.js.map +1 -1
  26. package/dist/components/dialogs/ExportSVGDialog.d.ts +3 -2
  27. package/dist/components/dialogs/ExportSVGDialog.js +4 -2
  28. package/dist/components/dialogs/ExportSVGDialog.js.map +1 -1
  29. package/dist/components/dialogs/FeatureDialog.js +15 -10
  30. package/dist/components/dialogs/FeatureDialog.js.map +1 -1
  31. package/dist/components/getVisibleLeaves.d.ts +1 -1
  32. package/dist/components/header/FileMenu.js +9 -0
  33. package/dist/components/header/FileMenu.js.map +1 -1
  34. package/dist/components/header/GappynessSlider.js +15 -3
  35. package/dist/components/header/GappynessSlider.js.map +1 -1
  36. package/dist/components/header/Header.js +6 -7
  37. package/dist/components/header/Header.js.map +1 -1
  38. package/dist/components/header/{HeaderStatusArea.d.ts → RowSearch.d.ts} +2 -2
  39. package/dist/components/header/RowSearch.js +15 -0
  40. package/dist/components/header/RowSearch.js.map +1 -0
  41. package/dist/components/header/revealRow.d.ts +3 -0
  42. package/dist/components/header/revealRow.js +15 -0
  43. package/dist/components/header/revealRow.js.map +1 -0
  44. package/dist/components/header/settingsMenuItems.js +57 -1
  45. package/dist/components/header/settingsMenuItems.js.map +1 -1
  46. package/dist/components/import/ImportForm.js +10 -4
  47. package/dist/components/import/ImportForm.js.map +1 -1
  48. package/dist/components/import/ImportFormExamples.js +19 -27
  49. package/dist/components/import/ImportFormExamples.js.map +1 -1
  50. package/dist/components/minimap/Minimap.js +6 -1
  51. package/dist/components/minimap/Minimap.js.map +1 -1
  52. package/dist/components/msa/AnnotationLegend.js +11 -5
  53. package/dist/components/msa/AnnotationLegend.js.map +1 -1
  54. package/dist/components/msa/MSACanvas.js +6 -1
  55. package/dist/components/msa/MSACanvas.js.map +1 -1
  56. package/dist/components/msa/MSACanvasBlock.js +3 -7
  57. package/dist/components/msa/MSACanvasBlock.js.map +1 -1
  58. package/dist/components/msa/msaKeys.d.ts +5 -0
  59. package/dist/components/msa/msaKeys.js +35 -0
  60. package/dist/components/msa/msaKeys.js.map +1 -0
  61. package/dist/components/msa/useMsaBlockMouse.d.ts +2 -4
  62. package/dist/components/msa/useMsaBlockMouse.js +18 -5
  63. package/dist/components/msa/useMsaBlockMouse.js.map +1 -1
  64. package/dist/components/rowpanels/RowPanelBlock.js +10 -14
  65. package/dist/components/rowpanels/RowPanelBlock.js.map +1 -1
  66. package/dist/components/tracks/drawTracks.d.ts +1 -0
  67. package/dist/components/tracks/drawTracks.js +14 -2
  68. package/dist/components/tracks/drawTracks.js.map +1 -1
  69. package/dist/components/tracks/useTrackHover.d.ts +1 -5
  70. package/dist/components/tracks/useTrackHover.js +5 -5
  71. package/dist/components/tracks/useTrackHover.js.map +1 -1
  72. package/dist/components/tree/ScaleBarSVG.d.ts +14 -0
  73. package/dist/components/tree/ScaleBarSVG.js +11 -0
  74. package/dist/components/tree/ScaleBarSVG.js.map +1 -0
  75. package/dist/components/tree/TreeCanvas.js +8 -26
  76. package/dist/components/tree/TreeCanvas.js.map +1 -1
  77. package/dist/components/tree/TreeCanvasBlock.js +11 -20
  78. package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
  79. package/dist/components/tree/{TreeBranchMenu.d.ts → TreeMenu.d.ts} +5 -4
  80. package/dist/components/tree/TreeMenu.js +48 -0
  81. package/dist/components/tree/TreeMenu.js.map +1 -0
  82. package/dist/components/tree/TreeRuler.js +3 -3
  83. package/dist/components/tree/TreeRuler.js.map +1 -1
  84. package/dist/components/tree/renderTreeCanvas.js +19 -9
  85. package/dist/components/tree/renderTreeCanvas.js.map +1 -1
  86. package/dist/components/tree/renderTreeMouseover.d.ts +5 -0
  87. package/dist/components/tree/renderTreeMouseover.js +20 -0
  88. package/dist/components/tree/renderTreeMouseover.js.map +1 -0
  89. package/dist/components/tree/useTreeHover.d.ts +3 -8
  90. package/dist/components/tree/useTreeHover.js +8 -10
  91. package/dist/components/tree/useTreeHover.js.map +1 -1
  92. package/dist/components/useHoverAnchor.d.ts +19 -0
  93. package/dist/components/useHoverAnchor.js +18 -0
  94. package/dist/components/useHoverAnchor.js.map +1 -0
  95. package/dist/constants.d.ts +4 -1
  96. package/dist/constants.js +12 -3
  97. package/dist/constants.js.map +1 -1
  98. package/dist/expandSpec.d.ts +46 -0
  99. package/dist/expandSpec.js +127 -0
  100. package/dist/expandSpec.js.map +1 -0
  101. package/dist/exportFileName.d.ts +3 -3
  102. package/dist/exportFileName.js +4 -4
  103. package/dist/exportFileName.js.map +1 -1
  104. package/dist/hierarchy.d.ts +51 -0
  105. package/dist/hierarchy.js +125 -0
  106. package/dist/hierarchy.js.map +1 -1
  107. package/dist/index.d.ts +4 -2
  108. package/dist/index.js +2 -0
  109. package/dist/index.js.map +1 -1
  110. package/dist/model/treeModel.d.ts +12 -0
  111. package/dist/model/treeModel.js +14 -1
  112. package/dist/model/treeModel.js.map +1 -1
  113. package/dist/model.d.ts +188 -47
  114. package/dist/model.js +482 -192
  115. package/dist/model.js.map +1 -1
  116. package/dist/renderToSvg.js +2 -4
  117. package/dist/renderToSvg.js.map +1 -1
  118. package/dist/rerootTree.d.ts +33 -0
  119. package/dist/rerootTree.js +144 -0
  120. package/dist/rerootTree.js.map +1 -0
  121. package/dist/svgTestUtil.d.ts +114 -20
  122. package/dist/types.d.ts +25 -12
  123. package/dist/useMsaSvgFigure.d.ts +2 -1
  124. package/dist/useMsaSvgFigure.js +11 -4
  125. package/dist/useMsaSvgFigure.js.map +1 -1
  126. package/dist/version.d.ts +1 -1
  127. package/dist/version.js +1 -1
  128. package/dist/writeNewick.d.ts +8 -0
  129. package/dist/writeNewick.js +34 -0
  130. package/dist/writeNewick.js.map +1 -0
  131. package/package.json +3 -3
  132. package/src/columnCounts.ts +14 -6
  133. package/src/components/CopyButton.tsx +2 -2
  134. package/src/components/DragHandle.tsx +21 -1
  135. package/src/components/Loading.tsx +14 -30
  136. package/src/components/MSAViewer.tsx +26 -0
  137. package/src/components/SequenceTextArea.tsx +54 -8
  138. package/src/components/StatusMessage.tsx +70 -0
  139. package/src/components/Track.tsx +8 -4
  140. package/src/components/VerticalScrollbar.tsx +7 -0
  141. package/src/components/dialogs/ExportSVGDialog.tsx +5 -2
  142. package/src/components/dialogs/FeatureDialog.tsx +59 -38
  143. package/src/components/header/FileMenu.tsx +9 -0
  144. package/src/components/header/GappynessSlider.tsx +17 -4
  145. package/src/components/header/Header.tsx +7 -8
  146. package/src/components/header/RowSearch.tsx +34 -0
  147. package/src/components/header/revealRow.ts +17 -0
  148. package/src/components/header/settingsMenuItems.ts +66 -0
  149. package/src/components/import/ImportForm.tsx +14 -7
  150. package/src/components/import/ImportFormExamples.tsx +20 -28
  151. package/src/components/minimap/Minimap.tsx +7 -0
  152. package/src/components/msa/AnnotationLegend.tsx +12 -6
  153. package/src/components/msa/MSACanvas.tsx +8 -0
  154. package/src/components/msa/MSACanvasBlock.tsx +5 -11
  155. package/src/components/msa/msaKeys.ts +39 -0
  156. package/src/components/msa/useMsaBlockMouse.ts +23 -7
  157. package/src/components/rowpanels/RowPanelBlock.tsx +16 -23
  158. package/src/components/tracks/drawTracks.ts +15 -2
  159. package/src/components/tracks/useTrackHover.ts +5 -5
  160. package/src/components/tree/ScaleBarSVG.tsx +30 -0
  161. package/src/components/tree/TreeCanvas.tsx +9 -40
  162. package/src/components/tree/TreeCanvasBlock.tsx +19 -39
  163. package/src/components/tree/TreeMenu.tsx +129 -0
  164. package/src/components/tree/TreeRuler.tsx +7 -13
  165. package/src/components/tree/renderTreeCanvas.ts +20 -8
  166. package/src/components/tree/renderTreeMouseover.ts +41 -0
  167. package/src/components/tree/useTreeHover.ts +8 -17
  168. package/src/components/useHoverAnchor.ts +31 -0
  169. package/src/constants.ts +14 -3
  170. package/src/expandSpec.ts +196 -0
  171. package/src/exportFileName.ts +4 -3
  172. package/src/hierarchy.ts +151 -4
  173. package/src/index.ts +11 -1
  174. package/src/model/treeModel.ts +22 -0
  175. package/src/model.ts +588 -221
  176. package/src/renderToSvg.tsx +6 -9
  177. package/src/rerootTree.ts +172 -0
  178. package/src/types.ts +34 -12
  179. package/src/useMsaSvgFigure.ts +11 -4
  180. package/src/version.ts +1 -1
  181. package/src/writeNewick.ts +38 -0
  182. package/dist/components/PortalTooltip.d.ts +0 -11
  183. package/dist/components/PortalTooltip.js +0 -29
  184. package/dist/components/PortalTooltip.js.map +0 -1
  185. package/dist/components/header/HeaderStatusArea.js +0 -21
  186. package/dist/components/header/HeaderStatusArea.js.map +0 -1
  187. package/dist/components/tree/TreeBranchMenu.js +0 -29
  188. package/dist/components/tree/TreeBranchMenu.js.map +0 -1
  189. package/dist/components/tree/TreeNodeMenu.d.ts +0 -13
  190. package/dist/components/tree/TreeNodeMenu.js +0 -41
  191. package/dist/components/tree/TreeNodeMenu.js.map +0 -1
  192. package/src/components/PortalTooltip.tsx +0 -47
  193. package/src/components/header/HeaderStatusArea.tsx +0 -32
  194. package/src/components/tree/TreeBranchMenu.tsx +0 -68
  195. package/src/components/tree/TreeNodeMenu.tsx +0 -95
package/dist/types.d.ts CHANGED
@@ -269,10 +269,27 @@ export interface Highlight {
269
269
  /**
270
270
  * What the viewer draws over a clade. `highlight` fills the rows behind it,
271
271
  * across the tree and the alignment. `bracket` draws a bar and the clade's
272
- * `label` in the gutter right of the tip labels. `collapse` and `focus` seed
273
- * the collapsed list and the subtree in focus at load.
272
+ * `label` in the gutter right of the tip labels. `collapse`, `focus` and
273
+ * `rotate` seed the collapsed list, the subtree in focus and the rotated nodes
274
+ * at load.
274
275
  */
275
- export type CladeMark = 'highlight' | 'bracket' | 'collapse' | 'focus';
276
+ export type CladeMark = 'highlight' | 'bracket' | 'collapse' | 'focus' | 'rotate';
277
+ /**
278
+ * The order each node's children draw in, top to bottom. `branchLength` puts
279
+ * the shortest branch first, `input` keeps the file's order, `ladderize` puts
280
+ * the clade with the fewest tips first, so the deep clades step down toward
281
+ * the bottom, and `ladderizeReverse` puts the one with the most tips first.
282
+ */
283
+ export type TreeOrder = 'branchLength' | 'input' | 'ladderize' | 'ladderizeReverse';
284
+ /**
285
+ * Where the tree is rooted: `midpoint` halfway along the longest path between
286
+ * two tips, and `{outgroup}` on the branch above those tips' most recent common
287
+ * ancestor, or above the rest of the tips' where the outgroup straddles the
288
+ * root the file gives.
289
+ */
290
+ export type TreeRoot = 'midpoint' | {
291
+ outgroup: string[];
292
+ };
276
293
  /**
277
294
  * A clade of the tree and the mark drawn over it. `mrca` names tips whose most
278
295
  * recent common ancestor is the clade; `range` names the first and last tip of
@@ -316,8 +333,9 @@ export interface Region {
316
333
  /**
317
334
  * A cell of the alignment in the host's coordinates, 1-based like Highlight:
318
335
  * `column` counts every column of the file, hidden gappy ones included, and
319
- * `residue` counts the row's own letters, absent on a gap. A pointer over a
320
- * track has a column and no row.
336
+ * `residue` counts the row's own letters, absent on a gap. `letter` is the
337
+ * character in the cell, the gap character (`-` or `.`) on a gap. A pointer
338
+ * over a track has a column and no row.
321
339
  */
322
340
  export interface Cell {
323
341
  column: number;
@@ -369,10 +387,5 @@ export interface NodeWithIds {
369
387
  length?: number;
370
388
  noTree?: boolean;
371
389
  }
372
- export interface NodeWithIdsAndLength {
373
- id: string;
374
- name: string;
375
- children: NodeWithIdsAndLength[];
376
- noTree?: boolean;
377
- length: number;
378
- }
390
+ /** @deprecated the same type as `NodeWithIds` */
391
+ export type NodeWithIdsAndLength = NodeWithIds;
@@ -8,7 +8,8 @@ export interface MsaSvgFigure {
8
8
  /**
9
9
  * The viewer's SVG export of its viewport, as markup for the page to insert.
10
10
  * It redraws after the view settles: `delay` ms after the last scroll, zoom,
11
- * resize or change to the snapshot. A draw a later one overtakes is dropped.
11
+ * resize, highlight or change to the snapshot. A draw a later one overtakes is
12
+ * dropped.
12
13
  *
13
14
  * The figure's row names and letters are SVG text, so the browser's find
14
15
  * locates them, a reader can select them, and print stylesheets apply.
@@ -1,12 +1,13 @@
1
1
  import { useEffect, useState } from 'react';
2
2
  import { createJBrowseTheme } from '@jbrowse/core/ui/theme';
3
3
  import { getSnapshot } from '@jbrowse/mobx-state-tree';
4
- import { compareStructural, reaction } from 'mobx';
4
+ import { compareShallow, reaction } from 'mobx';
5
5
  import { renderToSvg } from './renderToSvg.js';
6
6
  /**
7
7
  * The viewer's SVG export of its viewport, as markup for the page to insert.
8
8
  * It redraws after the view settles: `delay` ms after the last scroll, zoom,
9
- * resize or change to the snapshot. A draw a later one overtakes is dropped.
9
+ * resize, highlight or change to the snapshot. A draw a later one overtakes is
10
+ * dropped.
10
11
  *
11
12
  * The figure's row names and letters are SVG text, so the browser's find
12
13
  * locates them, a reader can select them, and print stylesheets apply.
@@ -16,7 +17,13 @@ export function useMsaSvgFigure(model, { theme, includeTracks = false, delay = 3
16
17
  useEffect(() => {
17
18
  let latest = 0;
18
19
  const dispose = reaction(() => model.viewInitialized && model.dataInitialized
19
- ? [getSnapshot(model), model.width, model.height]
20
+ ? [
21
+ getSnapshot(model),
22
+ model.width,
23
+ model.height,
24
+ model.resolvedHighlights,
25
+ model.highlightedColumnRuns,
26
+ ]
20
27
  : undefined, key => {
21
28
  if (!key) {
22
29
  return;
@@ -35,7 +42,7 @@ export function useMsaSvgFigure(model, { theme, includeTracks = false, delay = 3
35
42
  setFigure({ error });
36
43
  }
37
44
  });
38
- }, { delay, fireImmediately: true, equals: compareStructural });
45
+ }, { delay, fireImmediately: true, equals: compareShallow });
39
46
  return () => {
40
47
  latest = -1;
41
48
  dispose();
@@ -1 +1 @@
1
- {"version":3,"file":"useMsaSvgFigure.js","sourceRoot":"","sources":["../src/useMsaSvgFigure.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAE3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AACtD,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAA;AAElD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAmB,CAAA;AAW/C;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAC7B,KAAmB,EACnB,EACE,KAAK,EACL,aAAa,GAAG,KAAK,EACrB,KAAK,GAAG,GAAG,GACZ,GAA+D,EAAE;IAElE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAe,EAAE,CAAC,CAAA;IACtD,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,MAAM,GAAG,CAAC,CAAA;QACd,MAAM,OAAO,GAAG,QAAQ,CACtB,GAAG,EAAE,CACH,KAAK,CAAC,eAAe,IAAI,KAAK,CAAC,eAAe;YAC5C,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC;YACjD,CAAC,CAAC,SAAS,EACf,GAAG,CAAC,EAAE;YACJ,IAAI,CAAC,GAAG,EAAE,CAAC;gBACT,OAAM;YACR,CAAC;YACD,MAAM,IAAI,GAAG,EAAE,MAAM,CAAA;YACrB,WAAW,CAAC,KAAK,EAAE;gBACjB,KAAK,EAAE,KAAK,IAAI,kBAAkB,EAAE;gBACpC,UAAU,EAAE,UAAU;gBACtB,aAAa;aACd,CAAC,CAAC,IAAI,CACL,GAAG,CAAC,EAAE;gBACJ,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;oBACpB,SAAS,CAAC,EAAE,GAAG,EAAE,CAAC,CAAA;gBACpB,CAAC;YACH,CAAC,EACD,CAAC,KAAc,EAAE,EAAE;gBACjB,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;oBACpB,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC,CAAA;gBACtB,CAAC;YACH,CAAC,CACF,CAAA;QACH,CAAC,EACD,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAC5D,CAAA;QACD,OAAO,GAAG,EAAE;YACV,MAAM,GAAG,CAAC,CAAC,CAAA;YACX,OAAO,EAAE,CAAA;QACX,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,CAAC,CAAC,CAAA;IACxC,OAAO,MAAM,CAAA;AACf,CAAC"}
1
+ {"version":3,"file":"useMsaSvgFigure.js","sourceRoot":"","sources":["../src/useMsaSvgFigure.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAE3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AACtD,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAA;AAE/C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAmB,CAAA;AAW/C;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAC7B,KAAmB,EACnB,EACE,KAAK,EACL,aAAa,GAAG,KAAK,EACrB,KAAK,GAAG,GAAG,GACZ,GAA+D,EAAE;IAElE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAe,EAAE,CAAC,CAAA;IACtD,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,MAAM,GAAG,CAAC,CAAA;QACd,MAAM,OAAO,GAAG,QAAQ,CACtB,GAAG,EAAE,CACH,KAAK,CAAC,eAAe,IAAI,KAAK,CAAC,eAAe;YAC5C,CAAC,CAAC;gBACE,WAAW,CAAC,KAAK,CAAC;gBAClB,KAAK,CAAC,KAAK;gBACX,KAAK,CAAC,MAAM;gBACZ,KAAK,CAAC,kBAAkB;gBACxB,KAAK,CAAC,qBAAqB;aAC5B;YACH,CAAC,CAAC,SAAS,EACf,GAAG,CAAC,EAAE;YACJ,IAAI,CAAC,GAAG,EAAE,CAAC;gBACT,OAAM;YACR,CAAC;YACD,MAAM,IAAI,GAAG,EAAE,MAAM,CAAA;YACrB,WAAW,CAAC,KAAK,EAAE;gBACjB,KAAK,EAAE,KAAK,IAAI,kBAAkB,EAAE;gBACpC,UAAU,EAAE,UAAU;gBACtB,aAAa;aACd,CAAC,CAAC,IAAI,CACL,GAAG,CAAC,EAAE;gBACJ,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;oBACpB,SAAS,CAAC,EAAE,GAAG,EAAE,CAAC,CAAA;gBACpB,CAAC;YACH,CAAC,EACD,CAAC,KAAc,EAAE,EAAE;gBACjB,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;oBACpB,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC,CAAA;gBACtB,CAAC;YACH,CAAC,CACF,CAAA;QACH,CAAC,EACD,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,EAAE,cAAc,EAAE,CACzD,CAAA;QACD,OAAO,GAAG,EAAE;YACV,MAAM,GAAG,CAAC,CAAC,CAAA;YACX,OAAO,EAAE,CAAA;QACX,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,CAAC,CAAC,CAAA;IACxC,OAAO,MAAM,CAAA;AACf,CAAC"}
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const version = "8.1.0";
1
+ export declare const version = "8.3.0";
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
- export const version = '8.1.0';
1
+ export const version = '8.3.0';
2
2
  //# sourceMappingURL=version.js.map
@@ -0,0 +1,8 @@
1
+ import type { HierarchyNode } from './hierarchy.ts';
2
+ /**
3
+ * The tree as Newick, in the order it is drawn. An internal node whose name is
4
+ * only its generated id goes unnamed. Each subtree's string is dropped once its
5
+ * parent takes it in, since a caterpillar tree is as deep as it has tips and
6
+ * keeping every one is quadratic.
7
+ */
8
+ export declare function writeNewick(root: HierarchyNode): string;
@@ -0,0 +1,34 @@
1
+ import { descendants } from './hierarchy.js';
2
+ // a name holding any of these reads back as grammar unless it is quoted
3
+ const needsQuotes = /[\s(),:;[\]']/;
4
+ function quote(name) {
5
+ return needsQuotes.test(name) ? `'${name.replaceAll("'", "''")}'` : name;
6
+ }
7
+ /**
8
+ * The tree as Newick, in the order it is drawn. An internal node whose name is
9
+ * only its generated id goes unnamed. Each subtree's string is dropped once its
10
+ * parent takes it in, since a caterpillar tree is as deep as it has tips and
11
+ * keeping every one is quadratic.
12
+ */
13
+ export function writeNewick(root) {
14
+ const written = new Map();
15
+ for (const node of descendants(root).reverse()) {
16
+ const { id, name, length } = node.data;
17
+ let text = '';
18
+ if (node.children) {
19
+ text = `(${node.children.map(c => written.get(c)).join(',')})`;
20
+ for (const child of node.children) {
21
+ written.delete(child);
22
+ }
23
+ }
24
+ if (!(node.children && name === id)) {
25
+ text += quote(name);
26
+ }
27
+ if (length !== undefined) {
28
+ text += `:${length}`;
29
+ }
30
+ written.set(node, text);
31
+ }
32
+ return `${written.get(root)};`;
33
+ }
34
+ //# sourceMappingURL=writeNewick.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writeNewick.js","sourceRoot":"","sources":["../src/writeNewick.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAI5C,wEAAwE;AACxE,MAAM,WAAW,GAAG,eAAe,CAAA;AAEnC,SAAS,KAAK,CAAC,IAAY;IACzB,OAAO,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAA;AAC1E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,IAAmB;IAC7C,MAAM,OAAO,GAAG,IAAI,GAAG,EAAyB,CAAA;IAChD,KAAK,MAAM,IAAI,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;QAC/C,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,CAAA;QACtC,IAAI,IAAI,GAAG,EAAE,CAAA;QACb,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,IAAI,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAA;YAC/D,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAClC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;YACvB,CAAC;QACH,CAAC;QACD,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,KAAK,EAAE,CAAC,EAAE,CAAC;YACpC,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,CAAA;QACrB,CAAC;QACD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,IAAI,IAAI,IAAI,MAAM,EAAE,CAAA;QACtB,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IACzB,CAAC;IACD,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAE,GAAG,CAAA;AACjC,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "react-msaview",
3
3
  "author": "Colin",
4
- "version": "8.1.0",
4
+ "version": "8.3.0",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "repository": {
@@ -50,10 +50,10 @@
50
50
  },
51
51
  "dependencies": {
52
52
  "@gmod/newick": "^1.0.3",
53
- "@jbrowse/svgcanvas": "8.1.0",
53
+ "@jbrowse/svgcanvas": "8.3.0",
54
54
  "colord": "^2.10.0",
55
55
  "flatbush": "^4.6.2",
56
- "msa-parsers": "8.1.0"
56
+ "msa-parsers": "8.3.0"
57
57
  },
58
58
  "scripts": {
59
59
  "clean": "rimraf dist bundle",
@@ -63,15 +63,21 @@ export class ColumnCounts {
63
63
  this.totals = new Uint32Array(numColumns)
64
64
  }
65
65
 
66
+ /** tallies a row, counting the columns past a short row's end as gaps */
66
67
  addRow(row: string) {
67
- const { counts, totals, slotTotals } = this
68
- const n = Math.min(row.length, this.numColumns)
68
+ const { counts, totals, slotTotals, numColumns } = this
69
+ const n = Math.min(row.length, numColumns)
69
70
  for (let col = 0; col < n; col++) {
70
71
  const slot = slotOf(row.charCodeAt(col))
71
72
  counts[col * SLOTS + slot]!++
72
73
  totals[col]!++
73
74
  slotTotals[slot]!++
74
75
  }
76
+ for (let col = n; col < numColumns; col++) {
77
+ counts[col * SLOTS + DASH]!++
78
+ totals[col]!++
79
+ }
80
+ slotTotals[DASH]! += numColumns - n
75
81
  }
76
82
 
77
83
  add(col: number, letter: string) {
@@ -161,11 +167,13 @@ export const numResidueSlots = SLOTS
161
167
 
162
168
  /**
163
169
  * Tally counts from alignment rows (gapped sequence strings, one per row). The
164
- * column count follows the longest row, matching what a per-column tally of
165
- * ragged input produces.
170
+ * column count defaults to the longest row, and a shorter row counts as gapped
171
+ * to the end.
166
172
  */
167
- export function columnCountsFromRows(rows: string[]) {
168
- const numColumns = rows.reduce((max, row) => Math.max(max, row.length), 0)
173
+ export function columnCountsFromRows(
174
+ rows: string[],
175
+ numColumns = rows.reduce((max, row) => Math.max(max, row.length), 0),
176
+ ) {
169
177
  const counts = new ColumnCounts(numColumns)
170
178
  for (const row of rows) {
171
179
  counts.addRow(row)
@@ -12,7 +12,7 @@ export default function CopyButton({
12
12
  text,
13
13
  label = 'Copy to clipboard',
14
14
  }: {
15
- text: string
15
+ text: string | (() => string)
16
16
  label?: string
17
17
  }) {
18
18
  const [copied, setCopied] = useState(false)
@@ -30,7 +30,7 @@ export default function CopyButton({
30
30
  variant="contained"
31
31
  color="primary"
32
32
  onClick={() => {
33
- copy(text)
33
+ copy(typeof text === 'string' ? text : text())
34
34
  setCopied(true)
35
35
  clearTimeout(timer.current)
36
36
  timer.current = setTimeout(() => {
@@ -8,6 +8,10 @@ import type { Theme } from '@mui/material'
8
8
 
9
9
  export const scrollbarThumbFill = 'rgba(66,119,127,0.3)'
10
10
 
11
+ // the model's resizeHandleWidth feeds the layout, so a divider keeps its drawn
12
+ // width and widens its target with this much invisible margin each side
13
+ const hitSlop = 4
14
+
11
15
  // The scroll thumbs (minimap, vertical scrollbar) and the resize dividers are
12
16
  // one widget -- press, drag along an axis, report the pixel delta -- differing
13
17
  // only in the affordance they present, so the variant carries the whole
@@ -71,6 +75,10 @@ export default function DragHandle({
71
75
 
72
76
  return (
73
77
  <div
78
+ {...(variant === 'resizer' && {
79
+ role: 'separator',
80
+ 'aria-orientation': axis === 'x' ? 'vertical' : 'horizontal',
81
+ })}
74
82
  onMouseEnter={() => {
75
83
  setHovered(true)
76
84
  }}
@@ -87,6 +95,18 @@ export default function DragHandle({
87
95
  background: hovered ? fillHovered(theme) : fill(theme),
88
96
  ...style,
89
97
  }}
90
- />
98
+ >
99
+ {variant === 'resizer' ? (
100
+ <div
101
+ style={{
102
+ position: 'absolute',
103
+ zIndex: 101,
104
+ ...(axis === 'x'
105
+ ? { top: 0, bottom: 0, left: -hitSlop, right: -hitSlop }
106
+ : { left: 0, right: 0, top: -hitSlop, bottom: -hitSlop }),
107
+ }}
108
+ />
109
+ ) : null}
110
+ </div>
91
111
  )
92
112
  }
@@ -2,35 +2,15 @@ import React from 'react'
2
2
 
3
3
  import { ErrorMessage } from '@jbrowse/core/ui'
4
4
  import { ErrorBoundary } from '@jbrowse/core/ui/ErrorBoundary'
5
- import { Button, CircularProgress, Typography } from '@mui/material'
5
+ import { Button } from '@mui/material'
6
6
  import { observer } from 'mobx-react'
7
7
 
8
8
  import MSAView from './MSAView.tsx'
9
+ import StatusMessage from './StatusMessage.tsx'
9
10
  import ImportForm from './import/ImportForm.tsx'
10
11
 
11
12
  import type { MsaViewModel } from '../model.ts'
12
13
 
13
- const LoadingSpinner = observer(function ({ model }: { model: MsaViewModel }) {
14
- const { status } = model
15
- return (
16
- <div
17
- style={{ display: 'flex', alignItems: 'center', gap: 12, padding: 20 }}
18
- >
19
- <CircularProgress size={24} />
20
- <Typography variant="h6">{status?.msg ?? 'Loading...'}</Typography>
21
- {status?.onCancel ? (
22
- <Button
23
- variant="outlined"
24
- size="small"
25
- onClick={() => status.onCancel?.()}
26
- >
27
- Cancel
28
- </Button>
29
- ) : null}
30
- </div>
31
- )
32
- })
33
-
34
14
  const Reset = observer(function ({
35
15
  model,
36
16
  error,
@@ -58,12 +38,15 @@ const Loading = observer(function ({ model }: { model: MsaViewModel }) {
58
38
  const {
59
39
  isLoading,
60
40
  dataInitialized,
41
+ data,
61
42
  error,
62
43
  resetCount,
63
44
  msaFilehandle,
64
45
  treeFilehandle,
65
46
  } = model
66
- const hasPendingFilehandle = !!(msaFilehandle || treeFilehandle)
47
+ const hasData = !!(data.msa || data.tree)
48
+ // a failed load keeps its filehandle, so the error outranks the spinner
49
+ const loading = !error && !!(msaFilehandle || treeFilehandle || isLoading)
67
50
 
68
51
  return (
69
52
  <div>
@@ -76,18 +59,19 @@ const Loading = observer(function ({ model }: { model: MsaViewModel }) {
76
59
  >
77
60
  {dataInitialized ? (
78
61
  isLoading ? (
79
- <LoadingSpinner model={model} />
62
+ <StatusMessage model={model} variant="page" />
80
63
  ) : (
81
64
  <MSAView model={model} />
82
65
  )
83
- ) : error ? (
84
- // checked before the spinner: a failed load keeps its filehandle
85
- // (only an abort clears it), so hasPendingFilehandle stays true
66
+ ) : hasData ? (
86
67
  <Reset model={model} error={error} />
87
- ) : hasPendingFilehandle || isLoading ? (
88
- <LoadingSpinner model={model} />
89
68
  ) : (
90
- <ImportForm model={model} />
69
+ <>
70
+ {loading ? <StatusMessage model={model} variant="page" /> : null}
71
+ <div style={{ display: loading ? 'none' : undefined }}>
72
+ <ImportForm model={model} />
73
+ </div>
74
+ </>
91
75
  )}
92
76
  </ErrorBoundary>
93
77
  </div>
@@ -20,6 +20,8 @@ import type {
20
20
  ResidueEncoding,
21
21
  ResidueMapping,
22
22
  RowPanelSpec,
23
+ TreeOrder,
24
+ TreeRoot,
23
25
  Viewport,
24
26
  } from '../types.ts'
25
27
  import type { FileLocation as FileLocationType } from '@jbrowse/core/util/types'
@@ -94,6 +96,17 @@ export interface MSAViewerProps {
94
96
  autoTreeAreaWidth?: boolean
95
97
  /** draw branch lengths (default true); false draws a cladogram */
96
98
  showBranchLen?: boolean
99
+ /**
100
+ * the order each node's children draw in: `branchLength` (default,
101
+ * shortest first), `input` (as the file gives them), `ladderize` (fewest
102
+ * tips first) or `ladderizeReverse` (most tips first)
103
+ */
104
+ treeOrder?: TreeOrder
105
+ /**
106
+ * reroot the tree: `midpoint`, or `{outgroup: [tip names]}` on the branch
107
+ * above those tips' common ancestor
108
+ */
109
+ treeRoot?: TreeRoot
97
110
  /**
98
111
  * which channel `colorScheme` paints: `fill` (default) colors the background
99
112
  * of the cell a residue sits in, `color` colors the letter itself and leaves
@@ -215,6 +228,8 @@ function Viewer({
215
228
  treeAreaWidth,
216
229
  autoTreeAreaWidth,
217
230
  showBranchLen,
231
+ treeOrder,
232
+ treeRoot,
218
233
  region,
219
234
  hideHeader,
220
235
  theme,
@@ -258,6 +273,8 @@ function Viewer({
258
273
  ...(treeAreaWidth ? { treeAreaWidth } : {}),
259
274
  ...(autoTreeAreaWidth ? { autoTreeAreaWidth } : {}),
260
275
  ...(showBranchLen !== undefined ? { showBranchLen } : {}),
276
+ ...(treeOrder ? { treeOrder } : {}),
277
+ ...(treeRoot ? { treeRoot } : {}),
261
278
  ...(residueEncoding !== undefined
262
279
  ? { bgColor: residueEncoding === 'fill' }
263
280
  : {}),
@@ -317,6 +334,11 @@ function Viewer({
317
334
  model.setShowBranchLen(showBranchLen)
318
335
  }
319
336
  }, [model, showBranchLen])
337
+ useEffect(() => {
338
+ if (treeOrder !== undefined) {
339
+ model.setTreeOrder(treeOrder)
340
+ }
341
+ }, [model, treeOrder])
320
342
  useEffect(() => {
321
343
  if (residueEncoding !== undefined) {
322
344
  model.setBgColor(residueEncoding === 'fill')
@@ -336,6 +358,10 @@ function Viewer({
336
358
  useEffect(() => {
337
359
  model.setHighlights(JSON.parse(highlightsKey))
338
360
  }, [model, highlightsKey])
361
+ const treeRootKey = JSON.stringify(treeRoot ?? null)
362
+ useEffect(() => {
363
+ model.setTreeRoot(JSON.parse(treeRootKey) ?? undefined)
364
+ }, [model, treeRootKey])
339
365
  const cladesKey = JSON.stringify(clades ?? [])
340
366
  useEffect(() => {
341
367
  model.setClades(JSON.parse(cladesKey))
@@ -1,9 +1,10 @@
1
1
  import React, { useState } from 'react'
2
2
 
3
3
  import { makeStyles } from '@jbrowse/core/util/tss-react'
4
- import { TextField } from '@mui/material'
4
+ import { Button, TextField } from '@mui/material'
5
5
  import { getUngappedSequence } from 'msa-parsers'
6
6
 
7
+ import { saveAs } from '../vendor/fileSaver.ts'
7
8
  import Checkbox2 from './Checkbox2.tsx'
8
9
  import CopyButton from './CopyButton.tsx'
9
10
 
@@ -18,19 +19,59 @@ const useStyles = makeStyles()(theme => ({
18
19
  },
19
20
  }))
20
21
 
22
+ export const maxShownRecords = 100
23
+ export const maxShownChars = 200_000
24
+
25
+ function fastaRecord([name, seq]: [string, string], showGaps: boolean) {
26
+ return `>${name}\n${showGaps ? seq : getUngappedSequence(seq)}`
27
+ }
28
+
29
+ function isAllGaps(seq: string) {
30
+ return !/[^.-]/.test(seq)
31
+ }
32
+
33
+ /**
34
+ * The first records of `rows` as FASTA, up to `maxShownRecords` records or
35
+ * `maxShownChars` characters, whichever comes first. A textarea holding every
36
+ * row of a large alignment freezes the page.
37
+ */
38
+ export function fastaPreview(rows: [string, string][], showGaps: boolean) {
39
+ const shown: string[] = []
40
+ let chars = 0
41
+ for (const row of rows) {
42
+ if (shown.length >= maxShownRecords || chars >= maxShownChars) {
43
+ break
44
+ }
45
+ const record = fastaRecord(row, showGaps)
46
+ shown.push(record)
47
+ chars += record.length + 1
48
+ }
49
+ return { text: shown.join('\n'), hidden: rows.length - shown.length }
50
+ }
51
+
21
52
  export default function SequenceTextArea({ str }: { str: [string, string][] }) {
22
53
  const { classes } = useStyles()
23
54
  const [showGaps, setShowGaps] = useState(false)
24
55
  const [showEmpty, setShowEmpty] = useState(false)
25
56
 
26
- const disp = str
27
- .map(([s1, s2]) => [s1, showGaps ? s2 : getUngappedSequence(s2)] as const)
28
- .filter(([, s2]) => showEmpty || !!s2)
29
- .map(([s1, s2]) => `>${s1}\n${s2}`)
30
- .join('\n')
57
+ const rows = showEmpty
58
+ ? str
59
+ : str.filter(([, seq]) => (showGaps ? !!seq : !isAllGaps(seq)))
60
+ const { text, hidden } = fastaPreview(rows, showGaps)
61
+ const fullText = () => rows.map(row => fastaRecord(row, showGaps)).join('\n')
31
62
  return (
32
63
  <>
33
- <CopyButton text={disp} />
64
+ <CopyButton text={fullText} />
65
+ <Button
66
+ onClick={() => {
67
+ saveAs(
68
+ new Blob([fullText()], { type: 'text/plain' }),
69
+ 'sequences.fasta',
70
+ )
71
+ }}
72
+ >
73
+ Download
74
+ </Button>
34
75
  <Checkbox2
35
76
  label="Show gaps"
36
77
  checked={showGaps}
@@ -52,7 +93,12 @@ export default function SequenceTextArea({ str }: { str: [string, string][] }) {
52
93
  minRows={5}
53
94
  maxRows={10}
54
95
  fullWidth
55
- value={disp}
96
+ value={text}
97
+ helperText={
98
+ hidden > 0
99
+ ? `Showing ${rows.length - hidden} of ${rows.length.toLocaleString()} sequences. ${hidden.toLocaleString()} more are in the copy and the download.`
100
+ : undefined
101
+ }
56
102
  slotProps={{
57
103
  input: {
58
104
  readOnly: true,
@@ -0,0 +1,70 @@
1
+ import React from 'react'
2
+
3
+ import { LoadingEllipses } from '@jbrowse/core/ui'
4
+ import { makeStyles } from '@jbrowse/core/util/tss-react'
5
+ import { Button, CircularProgress, Typography } from '@mui/material'
6
+ import { observer } from 'mobx-react'
7
+
8
+ import type { MsaViewModel } from '../model.ts'
9
+
10
+ const useStyles = makeStyles()({
11
+ page: {
12
+ display: 'flex',
13
+ alignItems: 'center',
14
+ gap: 12,
15
+ padding: 20,
16
+ },
17
+ header: {
18
+ display: 'flex',
19
+ alignItems: 'center',
20
+ gap: 4,
21
+ margin: 'auto',
22
+ marginLeft: 10,
23
+ },
24
+ })
25
+
26
+ /**
27
+ * The model's status line with its Cancel button: a spinner and a heading in
28
+ * place of the view while it loads, or a line in the header afterwards, which
29
+ * disappears when there is no status.
30
+ */
31
+ const StatusMessage = observer(function ({
32
+ model,
33
+ variant,
34
+ }: {
35
+ model: MsaViewModel
36
+ variant: 'page' | 'header'
37
+ }) {
38
+ const { status } = model
39
+ const { classes } = useStyles()
40
+ if (variant === 'header' && !status) {
41
+ return null
42
+ }
43
+ const message = status?.msg ?? 'Loading...'
44
+ const onCancel = status?.onCancel
45
+ return (
46
+ <div className={classes[variant]}>
47
+ {variant === 'page' ? (
48
+ <>
49
+ <CircularProgress size={24} />
50
+ <Typography variant="h6">{message}</Typography>
51
+ </>
52
+ ) : (
53
+ <LoadingEllipses message={message} component="span" />
54
+ )}
55
+ {onCancel ? (
56
+ <Button
57
+ variant={variant === 'page' ? 'outlined' : 'text'}
58
+ size="small"
59
+ onClick={() => {
60
+ onCancel()
61
+ }}
62
+ >
63
+ Cancel
64
+ </Button>
65
+ ) : null}
66
+ </div>
67
+ )
68
+ })
69
+
70
+ export default StatusMessage
@@ -146,7 +146,7 @@ const Track = observer(function ({
146
146
  )
147
147
  useWheelScroll({ ref, onScrollX })
148
148
  const { classes } = useStyles()
149
- const { hover, onMouseMove, onMouseLeave } = useTrackHover({
149
+ const { anchor, onMouseMove, onMouseLeave } = useTrackHover({
150
150
  model,
151
151
  tooltip: showColumnStats,
152
152
  })
@@ -172,9 +172,13 @@ const Track = observer(function ({
172
172
  }}
173
173
  >
174
174
  <track.ReactComponent model={model} track={track} />
175
- {hover ? (
176
- <BaseTooltip clientPoint={{ x: hover.x, y: hover.y + 15 }}>
177
- <TrackTooltipContent model={model} track={track} col={hover.col} />
175
+ {anchor ? (
176
+ <BaseTooltip clientPoint={anchor.clientPoint}>
177
+ <TrackTooltipContent
178
+ model={model}
179
+ track={track}
180
+ col={anchor.value}
181
+ />
178
182
  </BaseTooltip>
179
183
  ) : null}
180
184
  </div>
@@ -23,6 +23,13 @@ const VerticalScrollbar = observer(({ model }: { model: MsaViewModel }) => {
23
23
 
24
24
  return (
25
25
  <div
26
+ onMouseDown={event => {
27
+ if (event.button === 0 && event.target === event.currentTarget) {
28
+ const y =
29
+ event.clientY - event.currentTarget.getBoundingClientRect().top
30
+ model.setScrollY(-y / unit + msaAreaHeight / 2)
31
+ }
32
+ }}
26
33
  style={{
27
34
  position: 'relative',
28
35
  width,