react-msaview 8.2.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 (192) 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.js +3 -3
  99. package/dist/expandSpec.js.map +1 -1
  100. package/dist/exportFileName.d.ts +3 -3
  101. package/dist/exportFileName.js +4 -4
  102. package/dist/exportFileName.js.map +1 -1
  103. package/dist/hierarchy.d.ts +51 -0
  104. package/dist/hierarchy.js +125 -0
  105. package/dist/hierarchy.js.map +1 -1
  106. package/dist/index.d.ts +2 -2
  107. package/dist/model/treeModel.d.ts +12 -0
  108. package/dist/model/treeModel.js +14 -1
  109. package/dist/model/treeModel.js.map +1 -1
  110. package/dist/model.d.ts +168 -47
  111. package/dist/model.js +435 -192
  112. package/dist/model.js.map +1 -1
  113. package/dist/renderToSvg.js +2 -4
  114. package/dist/renderToSvg.js.map +1 -1
  115. package/dist/rerootTree.d.ts +33 -0
  116. package/dist/rerootTree.js +144 -0
  117. package/dist/rerootTree.js.map +1 -0
  118. package/dist/svgTestUtil.d.ts +106 -20
  119. package/dist/types.d.ts +25 -12
  120. package/dist/useMsaSvgFigure.d.ts +2 -1
  121. package/dist/useMsaSvgFigure.js +11 -4
  122. package/dist/useMsaSvgFigure.js.map +1 -1
  123. package/dist/version.d.ts +1 -1
  124. package/dist/version.js +1 -1
  125. package/dist/writeNewick.d.ts +8 -0
  126. package/dist/writeNewick.js +34 -0
  127. package/dist/writeNewick.js.map +1 -0
  128. package/package.json +3 -3
  129. package/src/columnCounts.ts +14 -6
  130. package/src/components/CopyButton.tsx +2 -2
  131. package/src/components/DragHandle.tsx +21 -1
  132. package/src/components/Loading.tsx +14 -30
  133. package/src/components/MSAViewer.tsx +26 -0
  134. package/src/components/SequenceTextArea.tsx +54 -8
  135. package/src/components/StatusMessage.tsx +70 -0
  136. package/src/components/Track.tsx +8 -4
  137. package/src/components/VerticalScrollbar.tsx +7 -0
  138. package/src/components/dialogs/ExportSVGDialog.tsx +5 -2
  139. package/src/components/dialogs/FeatureDialog.tsx +59 -38
  140. package/src/components/header/FileMenu.tsx +9 -0
  141. package/src/components/header/GappynessSlider.tsx +17 -4
  142. package/src/components/header/Header.tsx +7 -8
  143. package/src/components/header/RowSearch.tsx +34 -0
  144. package/src/components/header/revealRow.ts +17 -0
  145. package/src/components/header/settingsMenuItems.ts +66 -0
  146. package/src/components/import/ImportForm.tsx +14 -7
  147. package/src/components/import/ImportFormExamples.tsx +20 -28
  148. package/src/components/minimap/Minimap.tsx +7 -0
  149. package/src/components/msa/AnnotationLegend.tsx +12 -6
  150. package/src/components/msa/MSACanvas.tsx +8 -0
  151. package/src/components/msa/MSACanvasBlock.tsx +5 -11
  152. package/src/components/msa/msaKeys.ts +39 -0
  153. package/src/components/msa/useMsaBlockMouse.ts +23 -7
  154. package/src/components/rowpanels/RowPanelBlock.tsx +16 -23
  155. package/src/components/tracks/drawTracks.ts +15 -2
  156. package/src/components/tracks/useTrackHover.ts +5 -5
  157. package/src/components/tree/ScaleBarSVG.tsx +30 -0
  158. package/src/components/tree/TreeCanvas.tsx +9 -40
  159. package/src/components/tree/TreeCanvasBlock.tsx +19 -39
  160. package/src/components/tree/TreeMenu.tsx +129 -0
  161. package/src/components/tree/TreeRuler.tsx +7 -13
  162. package/src/components/tree/renderTreeCanvas.ts +20 -8
  163. package/src/components/tree/renderTreeMouseover.ts +41 -0
  164. package/src/components/tree/useTreeHover.ts +8 -17
  165. package/src/components/useHoverAnchor.ts +31 -0
  166. package/src/constants.ts +14 -3
  167. package/src/expandSpec.ts +3 -3
  168. package/src/exportFileName.ts +4 -3
  169. package/src/hierarchy.ts +151 -4
  170. package/src/index.ts +3 -1
  171. package/src/model/treeModel.ts +22 -0
  172. package/src/model.ts +534 -223
  173. package/src/renderToSvg.tsx +6 -9
  174. package/src/rerootTree.ts +172 -0
  175. package/src/types.ts +34 -12
  176. package/src/useMsaSvgFigure.ts +11 -4
  177. package/src/version.ts +1 -1
  178. package/src/writeNewick.ts +38 -0
  179. package/dist/components/PortalTooltip.d.ts +0 -11
  180. package/dist/components/PortalTooltip.js +0 -29
  181. package/dist/components/PortalTooltip.js.map +0 -1
  182. package/dist/components/header/HeaderStatusArea.js +0 -21
  183. package/dist/components/header/HeaderStatusArea.js.map +0 -1
  184. package/dist/components/tree/TreeBranchMenu.js +0 -29
  185. package/dist/components/tree/TreeBranchMenu.js.map +0 -1
  186. package/dist/components/tree/TreeNodeMenu.d.ts +0 -13
  187. package/dist/components/tree/TreeNodeMenu.js +0 -41
  188. package/dist/components/tree/TreeNodeMenu.js.map +0 -1
  189. package/src/components/PortalTooltip.tsx +0 -47
  190. package/src/components/header/HeaderStatusArea.tsx +0 -32
  191. package/src/components/tree/TreeBranchMenu.tsx +0 -68
  192. package/src/components/tree/TreeNodeMenu.tsx +0 -95
@@ -14,6 +14,7 @@ import { visibleColRange } from './components/msa/visibleColRange.ts'
14
14
  import { headerRunsAcross } from './components/rowpanels/headerLayout.ts'
15
15
  import { renderRowPanels } from './components/rowpanels/renderRowPanel.ts'
16
16
  import { renderAllTracks } from './components/tracks/drawTracks.ts'
17
+ import ScaleBarSVG from './components/tree/ScaleBarSVG.tsx'
17
18
  import {
18
19
  bracketGap,
19
20
  cladeHeight,
@@ -323,17 +324,13 @@ function TreeScaleBarSVG({
323
324
  if (!bar) {
324
325
  return null
325
326
  }
326
- const y = treeOverviewHeight + treeScaleBarHeight
327
- const color = theme.palette.text.primary
328
327
  return (
329
328
  <g id="tree-scalebar">
330
- <text x={marginLeft} y={y - 9} fontSize={10} fill={color}>
331
- {bar.label}
332
- </text>
333
- <path
334
- d={`M${marginLeft} ${y - 7} v6 h${bar.px} v-6`}
335
- fill="none"
336
- stroke={color}
329
+ <ScaleBarSVG
330
+ bar={bar}
331
+ marginLeft={marginLeft}
332
+ y={treeOverviewHeight + treeScaleBarHeight}
333
+ color={theme.palette.text.primary}
337
334
  />
338
335
  </g>
339
336
  )
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Rerooting over the plain node `parseNewick` produces, the shape
3
+ * `@gmod/newick` and jbrowse-components' tree sidebar share, so these can move
4
+ * into that package unchanged.
5
+ *
6
+ * Every traversal is iterative, for the reason the ones in `@gmod/newick` are: a
7
+ * caterpillar tree is as deep as it has leaves, and recursion overflows the
8
+ * stack past about 5000 of them.
9
+ */
10
+ export interface TreeNode {
11
+ name?: string
12
+ length?: number
13
+ children?: TreeNode[]
14
+ }
15
+
16
+ function preorder<N extends TreeNode>(root: N) {
17
+ const order: N[] = []
18
+ const parents = new Map<N, N>()
19
+ const stack = [root]
20
+ while (stack.length > 0) {
21
+ const node = stack.pop()!
22
+ order.push(node)
23
+ for (const child of (node.children ?? []) as N[]) {
24
+ parents.set(child, node)
25
+ stack.push(child)
26
+ }
27
+ }
28
+ return { order, parents }
29
+ }
30
+
31
+ function hasLengths(order: TreeNode[]) {
32
+ return order.some(n => n.length !== undefined)
33
+ }
34
+
35
+ // the root's two children make one branch of the unrooted tree, which is the
36
+ // branch a rerooting at either of them splits
37
+ function mergedSibling<N extends TreeNode>(root: N, target: N) {
38
+ return root.children?.length === 2
39
+ ? (root.children.find(c => c !== target) as N | undefined)
40
+ : undefined
41
+ }
42
+
43
+ /**
44
+ * The tree rooted on the branch above `target`, `offset` up from it, or at the
45
+ * middle of that branch without one. Where the root has two children, their
46
+ * branches are one branch of the unrooted tree, and `offset` runs across both.
47
+ *
48
+ * The nodes between `target` and the old root turn over. A name on one of them
49
+ * labels the branch above it, which is where a support value sits, so the name
50
+ * follows that branch to the node now at its lower end. A two-child root has
51
+ * nothing left to split once one child leaves, so it drops out and its
52
+ * branches join.
53
+ */
54
+ export function rerootTree<N extends TreeNode>(
55
+ root: N,
56
+ target: N,
57
+ offset?: number,
58
+ ): TreeNode {
59
+ const { order, parents } = preorder(root)
60
+ if (!parents.has(target)) {
61
+ return root
62
+ }
63
+ const lengths = hasLengths(order)
64
+ const sibling =
65
+ parents.get(target) === root ? mergedSibling(root, target) : undefined
66
+ const branch = (target.length ?? 0) + (sibling?.length ?? 0)
67
+ const at = Math.min(Math.max(offset ?? branch / 2, 0), branch)
68
+ const length = (n: number) => (lengths ? n : undefined)
69
+
70
+ const path = [target]
71
+ for (let n = parents.get(target); n; n = parents.get(n)) {
72
+ path.push(n)
73
+ }
74
+ // Built from the old root down, so the node each turned node now hangs
75
+ // `above` it already exists. undefined where the old root dropped out.
76
+ let above: TreeNode | undefined
77
+ for (let i = path.length - 1; i >= 1; i--) {
78
+ const node = path[i]!
79
+ const below = path[i - 1]!
80
+ const children = node.children!.filter(c => c !== below)
81
+ if (above) {
82
+ children.push(above)
83
+ }
84
+ if (children.length === 0) {
85
+ above = undefined
86
+ } else if (i === path.length - 1 && children.length === 1) {
87
+ const only = children[0]!
88
+ above = {
89
+ ...only,
90
+ length: length(
91
+ (only.length ?? 0) + (below.length ?? 0) - (i === 1 ? at : 0),
92
+ ),
93
+ }
94
+ } else {
95
+ above = {
96
+ ...node,
97
+ name: i === 1 ? undefined : below.name,
98
+ children,
99
+ length: length((below.length ?? 0) - (i === 1 ? at : 0)),
100
+ }
101
+ }
102
+ }
103
+ return {
104
+ children: [{ ...target, length: length(at) }, ...(above ? [above] : [])],
105
+ }
106
+ }
107
+
108
+ /**
109
+ * The tree rooted halfway along the longest path between two tips, which is
110
+ * where a clock would put the root: the usual choice for the unrooted tree
111
+ * FastTree, IQ-TREE or neighbor joining writes. A tree with no branch lengths
112
+ * counts branches.
113
+ */
114
+ export function midpointRoot<N extends TreeNode>(root: N): TreeNode {
115
+ const { order, parents } = preorder(root)
116
+ const tips = order.filter(n => !n.children?.length)
117
+ if (tips.length < 2) {
118
+ return root
119
+ }
120
+ const lengths = hasLengths(order)
121
+ const weight = (n: N) => (lengths ? (n.length ?? 0) : 1)
122
+
123
+ // distance from `from` to every node over the unrooted tree, and the
124
+ // neighbor each was reached through
125
+ function reach(from: N) {
126
+ const dist = new Map<N, number>([[from, 0]])
127
+ const via = new Map<N, N>()
128
+ const stack = [from]
129
+ while (stack.length > 0) {
130
+ const node = stack.pop()!
131
+ const d = dist.get(node)!
132
+ const parent = parents.get(node)
133
+ if (parent && !dist.has(parent)) {
134
+ dist.set(parent, d + weight(node))
135
+ via.set(parent, node)
136
+ stack.push(parent)
137
+ }
138
+ for (const child of (node.children ?? []) as N[]) {
139
+ if (!dist.has(child)) {
140
+ dist.set(child, d + weight(child))
141
+ via.set(child, node)
142
+ stack.push(child)
143
+ }
144
+ }
145
+ }
146
+ return { dist, via }
147
+ }
148
+ function farthest(dist: Map<N, number>) {
149
+ return tips.reduce((best, tip) =>
150
+ dist.get(tip)! > dist.get(best)! ? tip : best,
151
+ )
152
+ }
153
+
154
+ const a = farthest(reach(tips[0]!).dist)
155
+ const { dist, via } = reach(a)
156
+ const b = farthest(dist)
157
+ if (b === a) {
158
+ return root
159
+ }
160
+ const half = dist.get(b)! / 2
161
+ let node = b
162
+ let next = via.get(node)!
163
+ while (dist.get(next)! > half) {
164
+ node = next
165
+ next = via.get(node)!
166
+ }
167
+ // the midpoint lies on the branch between `node` and `next`, and whichever
168
+ // of them is the child carries that branch
169
+ return parents.get(node) === next
170
+ ? rerootTree(root, node, dist.get(node)! - half)
171
+ : rerootTree(root, next, half - dist.get(next)!)
172
+ }
package/src/types.ts CHANGED
@@ -327,10 +327,36 @@ export interface Highlight {
327
327
  /**
328
328
  * What the viewer draws over a clade. `highlight` fills the rows behind it,
329
329
  * across the tree and the alignment. `bracket` draws a bar and the clade's
330
- * `label` in the gutter right of the tip labels. `collapse` and `focus` seed
331
- * the collapsed list and the subtree in focus at load.
330
+ * `label` in the gutter right of the tip labels. `collapse`, `focus` and
331
+ * `rotate` seed the collapsed list, the subtree in focus and the rotated nodes
332
+ * at load.
332
333
  */
333
- export type CladeMark = 'highlight' | 'bracket' | 'collapse' | 'focus'
334
+ export type CladeMark =
335
+ | 'highlight'
336
+ | 'bracket'
337
+ | 'collapse'
338
+ | 'focus'
339
+ | 'rotate'
340
+
341
+ /**
342
+ * The order each node's children draw in, top to bottom. `branchLength` puts
343
+ * the shortest branch first, `input` keeps the file's order, `ladderize` puts
344
+ * the clade with the fewest tips first, so the deep clades step down toward
345
+ * the bottom, and `ladderizeReverse` puts the one with the most tips first.
346
+ */
347
+ export type TreeOrder =
348
+ | 'branchLength'
349
+ | 'input'
350
+ | 'ladderize'
351
+ | 'ladderizeReverse'
352
+
353
+ /**
354
+ * Where the tree is rooted: `midpoint` halfway along the longest path between
355
+ * two tips, and `{outgroup}` on the branch above those tips' most recent common
356
+ * ancestor, or above the rest of the tips' where the outgroup straddles the
357
+ * root the file gives.
358
+ */
359
+ export type TreeRoot = 'midpoint' | { outgroup: string[] }
334
360
 
335
361
  /**
336
362
  * A clade of the tree and the mark drawn over it. `mrca` names tips whose most
@@ -378,8 +404,9 @@ export interface Region {
378
404
  /**
379
405
  * A cell of the alignment in the host's coordinates, 1-based like Highlight:
380
406
  * `column` counts every column of the file, hidden gappy ones included, and
381
- * `residue` counts the row's own letters, absent on a gap. A pointer over a
382
- * track has a column and no row.
407
+ * `residue` counts the row's own letters, absent on a gap. `letter` is the
408
+ * character in the cell, the gap character (`-` or `.`) on a gap. A pointer
409
+ * over a track has a column and no row.
383
410
  */
384
411
  export interface Cell {
385
412
  column: number
@@ -444,10 +471,5 @@ export interface NodeWithIds {
444
471
  noTree?: boolean
445
472
  }
446
473
 
447
- export interface NodeWithIdsAndLength {
448
- id: string
449
- name: string
450
- children: NodeWithIdsAndLength[]
451
- noTree?: boolean
452
- length: number
453
- }
474
+ /** @deprecated the same type as `NodeWithIds` */
475
+ export type NodeWithIdsAndLength = NodeWithIds
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'
2
2
 
3
3
  import { createJBrowseTheme } from '@jbrowse/core/ui/theme'
4
4
  import { getSnapshot } from '@jbrowse/mobx-state-tree'
5
- import { compareStructural, reaction } from 'mobx'
5
+ import { compareShallow, reaction } from 'mobx'
6
6
 
7
7
  import { renderToSvg } from './renderToSvg.tsx'
8
8
 
@@ -18,7 +18,8 @@ export interface MsaSvgFigure {
18
18
  /**
19
19
  * The viewer's SVG export of its viewport, as markup for the page to insert.
20
20
  * It redraws after the view settles: `delay` ms after the last scroll, zoom,
21
- * resize or change to the snapshot. A draw a later one overtakes is dropped.
21
+ * resize, highlight or change to the snapshot. A draw a later one overtakes is
22
+ * dropped.
22
23
  *
23
24
  * The figure's row names and letters are SVG text, so the browser's find
24
25
  * locates them, a reader can select them, and print stylesheets apply.
@@ -37,7 +38,13 @@ export function useMsaSvgFigure(
37
38
  const dispose = reaction(
38
39
  () =>
39
40
  model.viewInitialized && model.dataInitialized
40
- ? [getSnapshot(model), model.width, model.height]
41
+ ? [
42
+ getSnapshot(model),
43
+ model.width,
44
+ model.height,
45
+ model.resolvedHighlights,
46
+ model.highlightedColumnRuns,
47
+ ]
41
48
  : undefined,
42
49
  key => {
43
50
  if (!key) {
@@ -61,7 +68,7 @@ export function useMsaSvgFigure(
61
68
  },
62
69
  )
63
70
  },
64
- { delay, fireImmediately: true, equals: compareStructural },
71
+ { delay, fireImmediately: true, equals: compareShallow },
65
72
  )
66
73
  return () => {
67
74
  latest = -1
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '8.2.0'
1
+ export const version = '8.3.0'
@@ -0,0 +1,38 @@
1
+ import { descendants } from './hierarchy.ts'
2
+
3
+ import type { HierarchyNode } from './hierarchy.ts'
4
+
5
+ // a name holding any of these reads back as grammar unless it is quoted
6
+ const needsQuotes = /[\s(),:;[\]']/
7
+
8
+ function quote(name: string) {
9
+ return needsQuotes.test(name) ? `'${name.replaceAll("'", "''")}'` : name
10
+ }
11
+
12
+ /**
13
+ * The tree as Newick, in the order it is drawn. An internal node whose name is
14
+ * only its generated id goes unnamed. Each subtree's string is dropped once its
15
+ * parent takes it in, since a caterpillar tree is as deep as it has tips and
16
+ * keeping every one is quadratic.
17
+ */
18
+ export function writeNewick(root: HierarchyNode) {
19
+ const written = new Map<HierarchyNode, string>()
20
+ for (const node of descendants(root).reverse()) {
21
+ const { id, name, length } = node.data
22
+ let text = ''
23
+ if (node.children) {
24
+ text = `(${node.children.map(c => written.get(c)!).join(',')})`
25
+ for (const child of node.children) {
26
+ written.delete(child)
27
+ }
28
+ }
29
+ if (!(node.children && name === id)) {
30
+ text += quote(name)
31
+ }
32
+ if (length !== undefined) {
33
+ text += `:${length}`
34
+ }
35
+ written.set(node, text)
36
+ }
37
+ return `${written.get(root)!};`
38
+ }
@@ -1,11 +0,0 @@
1
- import React from 'react';
2
- /**
3
- * A tooltip beside the pointer, portaled to the body. A panel's scroll
4
- * transform is a containing block, so a fixed tooltip left inside it would
5
- * position against the scrolled content instead of the window.
6
- */
7
- export default function PortalTooltip({ clientX, clientY, children, }: {
8
- clientX: number;
9
- clientY: number;
10
- children: React.ReactNode;
11
- }): React.ReactPortal;
@@ -1,29 +0,0 @@
1
- import React from 'react';
2
- import { makeStyles } from '@jbrowse/core/util/tss-react';
3
- import { createPortal } from 'react-dom';
4
- const useStyles = makeStyles()(theme => ({
5
- tooltip: {
6
- position: 'fixed',
7
- pointerEvents: 'none',
8
- zIndex: 10000,
9
- backgroundColor: theme.palette.grey[700],
10
- color: theme.palette.common.white,
11
- padding: '4px 8px',
12
- borderRadius: 4,
13
- fontSize: 12,
14
- whiteSpace: 'nowrap',
15
- maxWidth: 300,
16
- overflow: 'hidden',
17
- textOverflow: 'ellipsis',
18
- },
19
- }));
20
- /**
21
- * A tooltip beside the pointer, portaled to the body. A panel's scroll
22
- * transform is a containing block, so a fixed tooltip left inside it would
23
- * position against the scrolled content instead of the window.
24
- */
25
- export default function PortalTooltip({ clientX, clientY, children, }) {
26
- const { classes } = useStyles();
27
- return createPortal(React.createElement("div", { className: classes.tooltip, style: { left: clientX + 12, top: clientY + 12 } }, children), document.body);
28
- }
29
- //# sourceMappingURL=PortalTooltip.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"PortalTooltip.js","sourceRoot":"","sources":["../../src/components/PortalTooltip.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAA;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAExC,MAAM,SAAS,GAAG,UAAU,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACvC,OAAO,EAAE;QACP,QAAQ,EAAE,OAAO;QACjB,aAAa,EAAE,MAAM;QACrB,MAAM,EAAE,KAAK;QACb,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC;QACxC,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK;QACjC,OAAO,EAAE,SAAS;QAClB,YAAY,EAAE,CAAC;QACf,QAAQ,EAAE,EAAE;QACZ,UAAU,EAAE,QAAQ;QACpB,QAAQ,EAAE,GAAG;QACb,QAAQ,EAAE,QAAQ;QAClB,YAAY,EAAE,UAAU;KACzB;CACF,CAAC,CAAC,CAAA;AAEH;;;;GAIG;AACH,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,EACpC,OAAO,EACP,OAAO,EACP,QAAQ,GAKT;IACC,MAAM,EAAE,OAAO,EAAE,GAAG,SAAS,EAAE,CAAA;IAC/B,OAAO,YAAY,CACjB,6BACE,SAAS,EAAE,OAAO,CAAC,OAAO,EAC1B,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,GAAG,EAAE,EAAE,GAAG,EAAE,OAAO,GAAG,EAAE,EAAE,IAE/C,QAAQ,CACL,EACN,QAAQ,CAAC,IAAI,CACd,CAAA;AACH,CAAC"}
@@ -1,21 +0,0 @@
1
- import React from 'react';
2
- import { LoadingEllipses } from '@jbrowse/core/ui';
3
- import { makeStyles } from '@jbrowse/core/util/tss-react';
4
- import { Button, Typography } from '@mui/material';
5
- import { observer } from 'mobx-react';
6
- const useStyles = makeStyles()({
7
- margin: {
8
- margin: 'auto',
9
- marginLeft: 10,
10
- },
11
- });
12
- const HeaderStatusArea = observer(({ model }) => {
13
- const { status } = model;
14
- const { classes } = useStyles();
15
- return status ? (React.createElement(Typography, { className: classes.margin },
16
- React.createElement(LoadingEllipses, { message: status.msg, component: "span" }),
17
- ' ',
18
- status.onCancel ? (React.createElement(Button, { size: "small", onClick: () => status.onCancel?.() }, "Cancel")) : null)) : null;
19
- });
20
- export default HeaderStatusArea;
21
- //# sourceMappingURL=HeaderStatusArea.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"HeaderStatusArea.js","sourceRoot":"","sources":["../../../src/components/header/HeaderStatusArea.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAA;AACzD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAIrC,MAAM,SAAS,GAAG,UAAU,EAAE,CAAC;IAC7B,MAAM,EAAE;QACN,MAAM,EAAE,MAAM;QACd,UAAU,EAAE,EAAE;KACf;CACF,CAAC,CAAA;AAEF,MAAM,gBAAgB,GAAG,QAAQ,CAAC,CAAC,EAAE,KAAK,EAA2B,EAAE,EAAE;IACvE,MAAM,EAAE,MAAM,EAAE,GAAG,KAAK,CAAA;IACxB,MAAM,EAAE,OAAO,EAAE,GAAG,SAAS,EAAE,CAAA;IAC/B,OAAO,MAAM,CAAC,CAAC,CAAC,CACd,oBAAC,UAAU,IAAC,SAAS,EAAE,OAAO,CAAC,MAAM;QACnC,oBAAC,eAAe,IAAC,OAAO,EAAE,MAAM,CAAC,GAAG,EAAE,SAAS,EAAC,MAAM,GAAG;QAAC,GAAG;QAC5D,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CACjB,oBAAC,MAAM,IAAC,IAAI,EAAC,OAAO,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,aAE9C,CACV,CAAC,CAAC,CAAC,IAAI,CACG,CACd,CAAC,CAAC,CAAC,IAAI,CAAA;AACV,CAAC,CAAC,CAAA;AAEF,eAAe,gBAAgB,CAAA"}
@@ -1,29 +0,0 @@
1
- import React from 'react';
2
- import { Menu, MenuItem } from '@mui/material';
3
- import { observer } from 'mobx-react';
4
- const TreeBranchMenu = observer(function ({ node, model, onClose, }) {
5
- return (React.createElement(Menu, { anchorReference: "anchorPosition", anchorPosition: {
6
- left: node.x,
7
- top: node.y,
8
- }, transitionDuration: 0, open: true, onClose: onClose },
9
- React.createElement(MenuItem, { dense: true, disabled: true }, node.name),
10
- React.createElement(MenuItem, { dense: true, onClick: () => {
11
- model.toggleCollapsed(node.id);
12
- onClose();
13
- } }, model.collapsed.includes(node.id)
14
- ? 'Expand this node'
15
- : 'Collapse this node'),
16
- React.createElement(MenuItem, { dense: true, onClick: () => {
17
- if (model.showOnly === node.id) {
18
- model.setShowOnly(undefined);
19
- }
20
- else {
21
- model.setShowOnly(node.id);
22
- }
23
- onClose();
24
- } }, model.showOnly === node.id
25
- ? 'Disable show only this node'
26
- : 'Show only this node')));
27
- });
28
- export default TreeBranchMenu;
29
- //# sourceMappingURL=TreeBranchMenu.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"TreeBranchMenu.js","sourceRoot":"","sources":["../../../src/components/tree/TreeBranchMenu.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAWrC,MAAM,cAAc,GAAG,QAAQ,CAAC,UAAU,EACxC,IAAI,EACJ,KAAK,EACL,OAAO,GAKR;IACC,OAAO,CACL,oBAAC,IAAI,IACH,eAAe,EAAC,gBAAgB,EAChC,cAAc,EAAE;YACd,IAAI,EAAE,IAAI,CAAC,CAAC;YACZ,GAAG,EAAE,IAAI,CAAC,CAAC;SACZ,EACD,kBAAkB,EAAE,CAAC,EACrB,IAAI,QACJ,OAAO,EAAE,OAAO;QAEhB,oBAAC,QAAQ,IAAC,KAAK,QAAC,QAAQ,UACrB,IAAI,CAAC,IAAI,CACD;QACX,oBAAC,QAAQ,IACP,KAAK,QACL,OAAO,EAAE,GAAG,EAAE;gBACZ,KAAK,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;gBAC9B,OAAO,EAAE,CAAA;YACX,CAAC,IAEA,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAChC,CAAC,CAAC,kBAAkB;YACpB,CAAC,CAAC,oBAAoB,CACf;QACX,oBAAC,QAAQ,IACP,KAAK,QACL,OAAO,EAAE,GAAG,EAAE;gBACZ,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI,CAAC,EAAE,EAAE,CAAC;oBAC/B,KAAK,CAAC,WAAW,CAAC,SAAS,CAAC,CAAA;gBAC9B,CAAC;qBAAM,CAAC;oBACN,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;gBAC5B,CAAC;gBACD,OAAO,EAAE,CAAA;YACX,CAAC,IAEA,KAAK,CAAC,QAAQ,KAAK,IAAI,CAAC,EAAE;YACzB,CAAC,CAAC,6BAA6B;YAC/B,CAAC,CAAC,qBAAqB,CAChB,CACN,CACR,CAAA;AACH,CAAC,CAAC,CAAA;AAEF,eAAe,cAAc,CAAA"}
@@ -1,13 +0,0 @@
1
- import React from 'react';
2
- import type { MsaViewModel } from '../../model.ts';
3
- declare const TreeMenu: ({ node, onClose, model, }: {
4
- node: {
5
- x: number;
6
- y: number;
7
- name: string;
8
- id: string;
9
- };
10
- model: MsaViewModel;
11
- onClose: () => void;
12
- }) => React.JSX.Element;
13
- export default TreeMenu;
@@ -1,41 +0,0 @@
1
- import React, { lazy } from 'react';
2
- import { Menu, MenuItem } from '@mui/material';
3
- import { observer } from 'mobx-react';
4
- // lazies
5
- const TreeNodeInfoDialog = lazy(() => import('./dialogs/TreeNodeInfoDialog.js'));
6
- const TreeMenu = observer(function ({ node, onClose, model, }) {
7
- const { collapsed } = model;
8
- const { name, id } = node;
9
- const isCollapsed = collapsed.includes(id);
10
- return (React.createElement(Menu, { anchorReference: "anchorPosition", anchorPosition: {
11
- top: node.y,
12
- left: node.x,
13
- }, transitionDuration: 0, open: true, onClose: onClose },
14
- React.createElement(MenuItem, { dense: true, disabled: true }, name),
15
- React.createElement(MenuItem, { dense: true, onClick: () => {
16
- model.queueDialog(onClose => [
17
- TreeNodeInfoDialog,
18
- {
19
- info: model.getRowData(name),
20
- model,
21
- nodeName: name,
22
- onClose,
23
- },
24
- ]);
25
- onClose();
26
- } }, "More info..."),
27
- React.createElement(MenuItem, { dense: true, onClick: () => {
28
- model.toggleCollapsed(id);
29
- onClose();
30
- } }, isCollapsed ? 'Expand subtree' : 'Collapse subtree'),
31
- React.createElement(MenuItem, { dense: true, onClick: () => {
32
- model.drawRelativeTo(node.name);
33
- onClose();
34
- } }, "Indicate differences from this row"),
35
- model.relativeTo ? (React.createElement(MenuItem, { dense: true, onClick: () => {
36
- model.drawRelativeTo(undefined);
37
- onClose();
38
- } }, "Clear reference row")) : null));
39
- });
40
- export default TreeMenu;
41
- //# sourceMappingURL=TreeNodeMenu.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"TreeNodeMenu.js","sourceRoot":"","sources":["../../../src/components/tree/TreeNodeMenu.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,OAAO,CAAA;AAEnC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAIrC,SAAS;AACT,MAAM,kBAAkB,GAAG,IAAI,CAC7B,GAAG,EAAE,CAAC,MAAM,CAAC,iCAAkC,CAAC,CACjD,CAAA;AAED,MAAM,QAAQ,GAAG,QAAQ,CAAC,UAAU,EAClC,IAAI,EACJ,OAAO,EACP,KAAK,GAUN;IACC,MAAM,EAAE,SAAS,EAAE,GAAG,KAAK,CAAA;IAC3B,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,IAAI,CAAA;IACzB,MAAM,WAAW,GAAG,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAA;IAC1C,OAAO,CACL,oBAAC,IAAI,IACH,eAAe,EAAC,gBAAgB,EAChC,cAAc,EAAE;YACd,GAAG,EAAE,IAAI,CAAC,CAAC;YACX,IAAI,EAAE,IAAI,CAAC,CAAC;SACb,EACD,kBAAkB,EAAE,CAAC,EACrB,IAAI,QACJ,OAAO,EAAE,OAAO;QAEhB,oBAAC,QAAQ,IAAC,KAAK,QAAC,QAAQ,UACrB,IAAI,CACI;QAEX,oBAAC,QAAQ,IACP,KAAK,QACL,OAAO,EAAE,GAAG,EAAE;gBACZ,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC;oBAC3B,kBAAkB;oBAClB;wBACE,IAAI,EAAE,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC;wBAC5B,KAAK;wBACL,QAAQ,EAAE,IAAI;wBACd,OAAO;qBACR;iBACF,CAAC,CAAA;gBACF,OAAO,EAAE,CAAA;YACX,CAAC,mBAGQ;QACX,oBAAC,QAAQ,IACP,KAAK,QACL,OAAO,EAAE,GAAG,EAAE;gBACZ,KAAK,CAAC,eAAe,CAAC,EAAE,CAAC,CAAA;gBACzB,OAAO,EAAE,CAAA;YACX,CAAC,IAEA,WAAW,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,kBAAkB,CAC3C;QACX,oBAAC,QAAQ,IACP,KAAK,QACL,OAAO,EAAE,GAAG,EAAE;gBACZ,KAAK,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;gBAC/B,OAAO,EAAE,CAAA;YACX,CAAC,yCAGQ;QACV,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAClB,oBAAC,QAAQ,IACP,KAAK,QACL,OAAO,EAAE,GAAG,EAAE;gBACZ,KAAK,CAAC,cAAc,CAAC,SAAS,CAAC,CAAA;gBAC/B,OAAO,EAAE,CAAA;YACX,CAAC,0BAGQ,CACZ,CAAC,CAAC,CAAC,IAAI,CACH,CACR,CAAA;AACH,CAAC,CAAC,CAAA;AAEF,eAAe,QAAQ,CAAA"}
@@ -1,47 +0,0 @@
1
- import React from 'react'
2
-
3
- import { makeStyles } from '@jbrowse/core/util/tss-react'
4
- import { createPortal } from 'react-dom'
5
-
6
- const useStyles = makeStyles()(theme => ({
7
- tooltip: {
8
- position: 'fixed',
9
- pointerEvents: 'none',
10
- zIndex: 10000,
11
- backgroundColor: theme.palette.grey[700],
12
- color: theme.palette.common.white,
13
- padding: '4px 8px',
14
- borderRadius: 4,
15
- fontSize: 12,
16
- whiteSpace: 'nowrap',
17
- maxWidth: 300,
18
- overflow: 'hidden',
19
- textOverflow: 'ellipsis',
20
- },
21
- }))
22
-
23
- /**
24
- * A tooltip beside the pointer, portaled to the body. A panel's scroll
25
- * transform is a containing block, so a fixed tooltip left inside it would
26
- * position against the scrolled content instead of the window.
27
- */
28
- export default function PortalTooltip({
29
- clientX,
30
- clientY,
31
- children,
32
- }: {
33
- clientX: number
34
- clientY: number
35
- children: React.ReactNode
36
- }) {
37
- const { classes } = useStyles()
38
- return createPortal(
39
- <div
40
- className={classes.tooltip}
41
- style={{ left: clientX + 12, top: clientY + 12 }}
42
- >
43
- {children}
44
- </div>,
45
- document.body,
46
- )
47
- }
@@ -1,32 +0,0 @@
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, Typography } from '@mui/material'
6
- import { observer } from 'mobx-react'
7
-
8
- import type { MsaViewModel } from '../../model.ts'
9
-
10
- const useStyles = makeStyles()({
11
- margin: {
12
- margin: 'auto',
13
- marginLeft: 10,
14
- },
15
- })
16
-
17
- const HeaderStatusArea = observer(({ model }: { model: MsaViewModel }) => {
18
- const { status } = model
19
- const { classes } = useStyles()
20
- return status ? (
21
- <Typography className={classes.margin}>
22
- <LoadingEllipses message={status.msg} component="span" />{' '}
23
- {status.onCancel ? (
24
- <Button size="small" onClick={() => status.onCancel?.()}>
25
- Cancel
26
- </Button>
27
- ) : null}
28
- </Typography>
29
- ) : null
30
- })
31
-
32
- export default HeaderStatusArea
@@ -1,68 +0,0 @@
1
- import React from 'react'
2
-
3
- import { Menu, MenuItem } from '@mui/material'
4
- import { observer } from 'mobx-react'
5
-
6
- import type { MsaViewModel } from '../../model.ts'
7
-
8
- interface Node {
9
- x: number
10
- y: number
11
- name: string
12
- id: string
13
- }
14
-
15
- const TreeBranchMenu = observer(function ({
16
- node,
17
- model,
18
- onClose,
19
- }: {
20
- node: Node
21
- model: MsaViewModel
22
- onClose: () => void
23
- }) {
24
- return (
25
- <Menu
26
- anchorReference="anchorPosition"
27
- anchorPosition={{
28
- left: node.x,
29
- top: node.y,
30
- }}
31
- transitionDuration={0}
32
- open
33
- onClose={onClose}
34
- >
35
- <MenuItem dense disabled>
36
- {node.name}
37
- </MenuItem>
38
- <MenuItem
39
- dense
40
- onClick={() => {
41
- model.toggleCollapsed(node.id)
42
- onClose()
43
- }}
44
- >
45
- {model.collapsed.includes(node.id)
46
- ? 'Expand this node'
47
- : 'Collapse this node'}
48
- </MenuItem>
49
- <MenuItem
50
- dense
51
- onClick={() => {
52
- if (model.showOnly === node.id) {
53
- model.setShowOnly(undefined)
54
- } else {
55
- model.setShowOnly(node.id)
56
- }
57
- onClose()
58
- }}
59
- >
60
- {model.showOnly === node.id
61
- ? 'Disable show only this node'
62
- : 'Show only this node'}
63
- </MenuItem>
64
- </Menu>
65
- )
66
- })
67
-
68
- export default TreeBranchMenu