jbrowse-plugin-mafviewer 1.4.3 → 1.4.6

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 (204) hide show
  1. package/README.md +1 -1
  2. package/dist/BigMafAdapter/BigMafAdapter.js +4 -5
  3. package/dist/BigMafAdapter/BigMafAdapter.js.map +1 -1
  4. package/dist/BigMafAdapter/configSchema.d.ts +2 -2
  5. package/dist/LinearMafDisplay/components/LinearMafDisplayComponent.js +39 -109
  6. package/dist/LinearMafDisplay/components/LinearMafDisplayComponent.js.map +1 -1
  7. package/dist/LinearMafDisplay/components/MAFTooltip.d.ts +0 -3
  8. package/dist/LinearMafDisplay/components/MAFTooltip.js.map +1 -1
  9. package/dist/LinearMafDisplay/components/MsaHighlightOverlay.d.ts +9 -0
  10. package/dist/LinearMafDisplay/components/MsaHighlightOverlay.js +34 -0
  11. package/dist/LinearMafDisplay/components/MsaHighlightOverlay.js.map +1 -0
  12. package/dist/LinearMafDisplay/components/Sidebar/ColorLegend.js +2 -2
  13. package/dist/LinearMafDisplay/components/Sidebar/ColorLegend.js.map +1 -1
  14. package/dist/LinearMafDisplay/components/Sidebar/RectBg.d.ts +1 -1
  15. package/dist/LinearMafDisplay/components/Sidebar/RectBg.js +2 -3
  16. package/dist/LinearMafDisplay/components/Sidebar/RectBg.js.map +1 -1
  17. package/dist/LinearMafDisplay/components/Sidebar/SvgWrapper.js +81 -11
  18. package/dist/LinearMafDisplay/components/Sidebar/SvgWrapper.js.map +1 -1
  19. package/dist/LinearMafDisplay/components/Sidebar/Tree.js +30 -9
  20. package/dist/LinearMafDisplay/components/Sidebar/Tree.js.map +1 -1
  21. package/dist/LinearMafDisplay/components/Sidebar/YScaleBars.d.ts +0 -1
  22. package/dist/LinearMafDisplay/components/Sidebar/YScaleBars.js.map +1 -1
  23. package/dist/LinearMafDisplay/components/useDragSelection.d.ts +25 -0
  24. package/dist/LinearMafDisplay/components/useDragSelection.js +103 -0
  25. package/dist/LinearMafDisplay/components/useDragSelection.js.map +1 -0
  26. package/dist/LinearMafDisplay/configSchema.d.ts +3 -30
  27. package/dist/LinearMafDisplay/renderSvg.js +1 -1
  28. package/dist/LinearMafDisplay/renderSvg.js.map +1 -1
  29. package/dist/LinearMafDisplay/stateModel.d.ts +1090 -102
  30. package/dist/LinearMafDisplay/stateModel.js +156 -17
  31. package/dist/LinearMafDisplay/stateModel.js.map +1 -1
  32. package/dist/LinearMafDisplay/types.d.ts +2 -2
  33. package/dist/LinearMafDisplay/util.d.ts +6 -0
  34. package/dist/LinearMafDisplay/util.js +28 -6
  35. package/dist/LinearMafDisplay/util.js.map +1 -1
  36. package/dist/LinearMafRenderer/LinearMafRenderer.d.ts +43 -10
  37. package/dist/LinearMafRenderer/LinearMafRenderer.js +1 -1
  38. package/dist/LinearMafRenderer/LinearMafRenderer.js.map +1 -1
  39. package/dist/LinearMafRenderer/components/LinearMafRendering.d.ts +14 -5
  40. package/dist/LinearMafRenderer/components/LinearMafRendering.js +40 -20
  41. package/dist/LinearMafRenderer/components/LinearMafRendering.js.map +1 -1
  42. package/dist/LinearMafRenderer/configSchema.d.ts +1 -6
  43. package/dist/LinearMafRenderer/configSchema.js +1 -6
  44. package/dist/LinearMafRenderer/configSchema.js.map +1 -1
  45. package/dist/LinearMafRenderer/makeImageData.js +6 -7
  46. package/dist/LinearMafRenderer/makeImageData.js.map +1 -1
  47. package/dist/LinearMafRenderer/rendering/features.d.ts +0 -1
  48. package/dist/LinearMafRenderer/rendering/features.js +1 -14
  49. package/dist/LinearMafRenderer/rendering/features.js.map +1 -1
  50. package/dist/LinearMafRenderer/rendering/insertions.d.ts +1 -1
  51. package/dist/LinearMafRenderer/rendering/insertions.js +10 -8
  52. package/dist/LinearMafRenderer/rendering/insertions.js.map +1 -1
  53. package/dist/LinearMafRenderer/rendering/matches.d.ts +1 -1
  54. package/dist/LinearMafRenderer/rendering/matches.js +3 -15
  55. package/dist/LinearMafRenderer/rendering/matches.js.map +1 -1
  56. package/dist/LinearMafRenderer/rendering/mismatches.d.ts +1 -1
  57. package/dist/LinearMafRenderer/rendering/mismatches.js +3 -3
  58. package/dist/LinearMafRenderer/rendering/spatialIndex.js +8 -2
  59. package/dist/LinearMafRenderer/rendering/spatialIndex.js.map +1 -1
  60. package/dist/LinearMafRenderer/rendering/text.js +1 -3
  61. package/dist/LinearMafRenderer/rendering/text.js.map +1 -1
  62. package/dist/LinearMafRenderer/rendering/types.d.ts +6 -5
  63. package/dist/MafAddTrackWorkflow/AddTrackWorkflow.js +1 -1
  64. package/dist/MafAddTrackWorkflow/AddTrackWorkflow.js.map +1 -1
  65. package/dist/MafAddTrackWorkflow/index.js +1 -1
  66. package/dist/MafAddTrackWorkflow/index.js.map +1 -1
  67. package/dist/MafGetSequences/MafGetSequences.d.ts +1 -0
  68. package/dist/MafGetSequences/MafGetSequences.js +2 -1
  69. package/dist/MafGetSequences/MafGetSequences.js.map +1 -1
  70. package/dist/MafSequenceWidget/LabelsCanvas.d.ts +8 -0
  71. package/dist/MafSequenceWidget/LabelsCanvas.js +37 -0
  72. package/dist/MafSequenceWidget/LabelsCanvas.js.map +1 -0
  73. package/dist/MafSequenceWidget/MafSequenceHoverHighlight.d.ts +6 -0
  74. package/dist/MafSequenceWidget/MafSequenceHoverHighlight.js +52 -0
  75. package/dist/MafSequenceWidget/MafSequenceHoverHighlight.js.map +1 -0
  76. package/dist/MafSequenceWidget/MafSequenceHoverHighlightExtension.d.ts +2 -0
  77. package/dist/MafSequenceWidget/MafSequenceHoverHighlightExtension.js +12 -0
  78. package/dist/MafSequenceWidget/MafSequenceHoverHighlightExtension.js.map +1 -0
  79. package/dist/MafSequenceWidget/MafSequenceWidget.d.ts +6 -0
  80. package/dist/MafSequenceWidget/MafSequenceWidget.js +189 -0
  81. package/dist/MafSequenceWidget/MafSequenceWidget.js.map +1 -0
  82. package/dist/MafSequenceWidget/SequenceCanvas.d.ts +12 -0
  83. package/dist/MafSequenceWidget/SequenceCanvas.js +86 -0
  84. package/dist/MafSequenceWidget/SequenceCanvas.js.map +1 -0
  85. package/dist/MafSequenceWidget/SequenceDisplay.d.ts +12 -0
  86. package/dist/MafSequenceWidget/SequenceDisplay.js +117 -0
  87. package/dist/MafSequenceWidget/SequenceDisplay.js.map +1 -0
  88. package/dist/MafSequenceWidget/SequenceTooltip.d.ts +11 -0
  89. package/dist/MafSequenceWidget/SequenceTooltip.js +39 -0
  90. package/dist/MafSequenceWidget/SequenceTooltip.js.map +1 -0
  91. package/dist/MafSequenceWidget/baseColors.d.ts +3 -0
  92. package/dist/MafSequenceWidget/baseColors.js +64 -0
  93. package/dist/MafSequenceWidget/baseColors.js.map +1 -0
  94. package/dist/MafSequenceWidget/colToGenomePos.d.ts +13 -0
  95. package/dist/MafSequenceWidget/colToGenomePos.js +32 -0
  96. package/dist/MafSequenceWidget/colToGenomePos.js.map +1 -0
  97. package/dist/MafSequenceWidget/colToGenomePos.test.d.ts +1 -0
  98. package/dist/MafSequenceWidget/colToGenomePos.test.js +136 -0
  99. package/dist/MafSequenceWidget/colToGenomePos.test.js.map +1 -0
  100. package/dist/MafSequenceWidget/configSchema.d.ts +1 -0
  101. package/dist/MafSequenceWidget/configSchema.js +3 -0
  102. package/dist/MafSequenceWidget/configSchema.js.map +1 -0
  103. package/dist/MafSequenceWidget/constants.d.ts +4 -0
  104. package/dist/MafSequenceWidget/constants.js +5 -0
  105. package/dist/MafSequenceWidget/constants.js.map +1 -0
  106. package/dist/MafSequenceWidget/index.d.ts +2 -0
  107. package/dist/MafSequenceWidget/index.js +16 -0
  108. package/dist/MafSequenceWidget/index.js.map +1 -0
  109. package/dist/MafSequenceWidget/stateModelFactory.d.ts +67 -0
  110. package/dist/MafSequenceWidget/stateModelFactory.js +21 -0
  111. package/dist/MafSequenceWidget/stateModelFactory.js.map +1 -0
  112. package/dist/MafTabixAdapter/MafTabixAdapter.js +4 -35
  113. package/dist/MafTabixAdapter/MafTabixAdapter.js.map +1 -1
  114. package/dist/MafTabixAdapter/configSchema.d.ts +4 -4
  115. package/dist/MafTrack/configSchema.d.ts +16 -11
  116. package/dist/index.js +2 -0
  117. package/dist/index.js.map +1 -1
  118. package/dist/jbrowse-plugin-mafviewer.umd.production.min.js +12 -24
  119. package/dist/jbrowse-plugin-mafviewer.umd.production.min.js.map +4 -4
  120. package/dist/util/clipboard.d.ts +2 -0
  121. package/dist/util/clipboard.js +28 -0
  122. package/dist/util/clipboard.js.map +1 -0
  123. package/dist/util/fastaUtils.d.ts +2 -1
  124. package/dist/util/fastaUtils.js +93 -50
  125. package/dist/util/fastaUtils.js.map +1 -1
  126. package/dist/util/fastaUtils.test.js +190 -0
  127. package/dist/util/fastaUtils.test.js.map +1 -1
  128. package/dist/util/parseAssemblyName.d.ts +32 -0
  129. package/dist/util/parseAssemblyName.js +87 -0
  130. package/dist/util/parseAssemblyName.js.map +1 -0
  131. package/dist/util/parseAssemblyName.test.d.ts +1 -0
  132. package/dist/util/parseAssemblyName.test.js +269 -0
  133. package/dist/util/parseAssemblyName.test.js.map +1 -0
  134. package/package.json +12 -12
  135. package/src/BigMafAdapter/BigMafAdapter.ts +5 -5
  136. package/src/LinearMafDisplay/components/LinearMafDisplayComponent.tsx +63 -145
  137. package/src/LinearMafDisplay/components/MAFTooltip.tsx +0 -3
  138. package/src/LinearMafDisplay/components/MsaHighlightOverlay.tsx +62 -0
  139. package/src/LinearMafDisplay/components/Sidebar/ColorLegend.tsx +2 -6
  140. package/src/LinearMafDisplay/components/Sidebar/RectBg.tsx +8 -3
  141. package/src/LinearMafDisplay/components/Sidebar/SvgWrapper.tsx +117 -15
  142. package/src/LinearMafDisplay/components/Sidebar/Tree.tsx +53 -8
  143. package/src/LinearMafDisplay/components/Sidebar/YScaleBars.tsx +0 -1
  144. package/src/LinearMafDisplay/components/useDragSelection.ts +159 -0
  145. package/src/LinearMafDisplay/renderSvg.tsx +1 -1
  146. package/src/LinearMafDisplay/stateModel.ts +215 -20
  147. package/src/LinearMafDisplay/types.ts +2 -2
  148. package/src/LinearMafDisplay/util.ts +35 -7
  149. package/src/LinearMafRenderer/LinearMafRenderer.ts +3 -5
  150. package/src/LinearMafRenderer/components/LinearMafRendering.tsx +67 -33
  151. package/src/LinearMafRenderer/configSchema.ts +1 -6
  152. package/src/LinearMafRenderer/makeImageData.ts +5 -14
  153. package/src/LinearMafRenderer/rendering/features.ts +2 -36
  154. package/src/LinearMafRenderer/rendering/insertions.ts +13 -8
  155. package/src/LinearMafRenderer/rendering/matches.ts +2 -27
  156. package/src/LinearMafRenderer/rendering/mismatches.ts +3 -3
  157. package/src/LinearMafRenderer/rendering/spatialIndex.ts +9 -2
  158. package/src/LinearMafRenderer/rendering/text.ts +1 -2
  159. package/src/LinearMafRenderer/rendering/types.ts +8 -5
  160. package/src/MafAddTrackWorkflow/AddTrackWorkflow.tsx +1 -1
  161. package/src/MafAddTrackWorkflow/index.ts +1 -1
  162. package/src/MafGetSequences/MafGetSequences.ts +10 -2
  163. package/src/MafSequenceWidget/LabelsCanvas.tsx +58 -0
  164. package/src/MafSequenceWidget/MafSequenceHoverHighlight.tsx +83 -0
  165. package/src/MafSequenceWidget/MafSequenceHoverHighlightExtension.tsx +24 -0
  166. package/src/MafSequenceWidget/MafSequenceWidget.tsx +294 -0
  167. package/src/MafSequenceWidget/SequenceCanvas.tsx +136 -0
  168. package/src/MafSequenceWidget/SequenceDisplay.tsx +188 -0
  169. package/src/MafSequenceWidget/SequenceTooltip.tsx +70 -0
  170. package/src/MafSequenceWidget/baseColors.ts +76 -0
  171. package/src/MafSequenceWidget/colToGenomePos.test.ts +166 -0
  172. package/src/MafSequenceWidget/colToGenomePos.ts +40 -0
  173. package/src/MafSequenceWidget/configSchema.ts +3 -0
  174. package/src/MafSequenceWidget/constants.ts +4 -0
  175. package/src/MafSequenceWidget/index.ts +24 -0
  176. package/src/MafSequenceWidget/stateModelFactory.ts +43 -0
  177. package/src/MafTabixAdapter/MafTabixAdapter.ts +12 -51
  178. package/src/index.ts +2 -0
  179. package/src/util/__snapshots__/fastaUtils.test.ts.snap +35 -0
  180. package/src/util/clipboard.ts +35 -0
  181. package/src/util/fastaUtils.test.ts +199 -0
  182. package/src/util/fastaUtils.ts +118 -51
  183. package/src/util/parseAssemblyName.test.ts +350 -0
  184. package/src/util/parseAssemblyName.ts +106 -0
  185. package/dist/LinearMafDisplay/components/GetSequenceDialog/GetSequenceDialog.d.ts +0 -11
  186. package/dist/LinearMafDisplay/components/GetSequenceDialog/GetSequenceDialog.js +0 -97
  187. package/dist/LinearMafDisplay/components/GetSequenceDialog/GetSequenceDialog.js.map +0 -1
  188. package/dist/LinearMafDisplay/components/util.d.ts +0 -1
  189. package/dist/LinearMafDisplay/components/util.js +0 -8
  190. package/dist/LinearMafDisplay/components/util.js.map +0 -1
  191. package/dist/LinearMafRenderer/components/util.d.ts +0 -1
  192. package/dist/LinearMafRenderer/components/util.js +0 -13
  193. package/dist/LinearMafRenderer/components/util.js.map +0 -1
  194. package/dist/util/fetchSequences.d.ts +0 -18
  195. package/dist/util/fetchSequences.js +0 -39
  196. package/dist/util/fetchSequences.js.map +0 -1
  197. package/dist/util/useSequences.d.ts +0 -21
  198. package/dist/util/useSequences.js +0 -64
  199. package/dist/util/useSequences.js.map +0 -1
  200. package/src/LinearMafDisplay/components/GetSequenceDialog/GetSequenceDialog.tsx +0 -175
  201. package/src/LinearMafDisplay/components/util.ts +0 -7
  202. package/src/LinearMafRenderer/components/util.ts +0 -13
  203. package/src/util/fetchSequences.ts +0 -57
  204. package/src/util/useSequences.ts +0 -90
@@ -1,15 +1,23 @@
1
1
  import { lazy } from 'react'
2
2
 
3
3
  import { ConfigurationReference, getConf } from '@jbrowse/core/configuration'
4
- import { getEnv, getSession, max, measureText } from '@jbrowse/core/util'
4
+ import {
5
+ SessionWithWidgets,
6
+ getContainingTrack,
7
+ getContainingView,
8
+ getEnv,
9
+ getSession,
10
+ max,
11
+ measureText,
12
+ } from '@jbrowse/core/util'
5
13
  import { getRpcSessionId } from '@jbrowse/core/util/tracks'
14
+ import { addDisposer, isAlive, types } from '@jbrowse/mobx-state-tree'
6
15
  import { ascending } from 'd3-array'
7
16
  import { cluster, hierarchy } from 'd3-hierarchy'
8
17
  import deepEqual from 'fast-deep-equal'
9
18
  import { autorun } from 'mobx'
10
- import { addDisposer, isAlive, types } from 'mobx-state-tree'
11
19
 
12
- import { maxLength, setBrLength } from './util'
20
+ import { computeNodeDescendantNames, maxLength, setBrLength } from './util'
13
21
  import { normalize } from '../util'
14
22
 
15
23
  import type { NodeWithIds, NodeWithIdsAndLength, Sample } from './types'
@@ -18,9 +26,18 @@ import type {
18
26
  AnyConfigurationModel,
19
27
  AnyConfigurationSchemaType,
20
28
  } from '@jbrowse/core/configuration'
29
+ import type { Instance } from '@jbrowse/mobx-state-tree'
21
30
  import type { ExportSvgDisplayOptions } from '@jbrowse/plugin-linear-genome-view'
22
31
  import type { HierarchyNode } from 'd3-hierarchy'
23
- import type { Instance } from 'mobx-state-tree'
32
+
33
+ const defaultRowHeight = 15
34
+ const defaultRowProportion = 0.8
35
+ const defaultShowAllLetters = false
36
+ const defaultMismatchRendering = true
37
+ const defaultShowBranchLen = false
38
+ const defaultTreeAreaWidth = 80
39
+ const defaultShowAsUpperCase = true
40
+ const defaultShowSidebar = true
24
41
 
25
42
  const SetRowHeightDialog = lazy(
26
43
  () => import('./components/SetRowHeightDialog/SetRowHeightDialog'),
@@ -55,33 +72,37 @@ export default function stateModelFactory(
55
72
  /**
56
73
  * #property
57
74
  */
58
- rowHeight: 15,
75
+ rowHeight: defaultRowHeight,
59
76
  /**
60
77
  * #property
61
78
  */
62
- rowProportion: 0.8,
79
+ rowProportion: defaultRowProportion,
63
80
  /**
64
81
  * #property
65
82
  */
66
- showAllLetters: false,
83
+ showAllLetters: defaultShowAllLetters,
67
84
  /**
68
85
  * #property
69
86
  */
70
- mismatchRendering: true,
87
+ mismatchRendering: defaultMismatchRendering,
71
88
 
72
89
  /**
73
90
  * #property
74
91
  */
75
- showBranchLen: false,
92
+ showBranchLen: defaultShowBranchLen,
76
93
 
77
94
  /**
78
95
  * #property
79
96
  */
80
- treeAreaWidth: 80,
97
+ treeAreaWidth: defaultTreeAreaWidth,
98
+ /**
99
+ * #property
100
+ */
101
+ showAsUpperCase: defaultShowAsUpperCase,
81
102
  /**
82
103
  * #property
83
104
  */
84
- showAsUpperCase: true,
105
+ showSidebar: defaultShowSidebar,
85
106
  }),
86
107
  )
87
108
  .volatile(() => ({
@@ -100,7 +121,15 @@ export default function stateModelFactory(
100
121
  /**
101
122
  * #volatile
102
123
  */
103
- volatileTree: undefined as any,
124
+ volatileTree: undefined as NodeWithIds | undefined,
125
+ /**
126
+ * #volatile
127
+ */
128
+ highlightedRowNames: undefined as string[] | undefined,
129
+ /**
130
+ * #volatile
131
+ */
132
+ hoveredTreeNode: undefined as { x: number; y: number } | undefined,
104
133
  }))
105
134
  .actions(self => ({
106
135
  /**
@@ -136,7 +165,13 @@ export default function stateModelFactory(
136
165
  /**
137
166
  * #action
138
167
  */
139
- setSamples({ samples, tree }: { samples: Sample[]; tree: unknown }) {
168
+ setSamples({
169
+ samples,
170
+ tree,
171
+ }: {
172
+ samples: Sample[]
173
+ tree: NodeWithIds | undefined
174
+ }) {
140
175
  if (!deepEqual(samples, self.volatileSamples)) {
141
176
  self.volatileSamples = samples
142
177
  }
@@ -150,6 +185,62 @@ export default function stateModelFactory(
150
185
  setShowAsUpperCase(arg: boolean) {
151
186
  self.showAsUpperCase = arg
152
187
  },
188
+ /**
189
+ * #action
190
+ */
191
+ setTreeAreaWidth(width: number) {
192
+ self.treeAreaWidth = width
193
+ },
194
+ /**
195
+ * #action
196
+ */
197
+ setShowSidebar(arg: boolean) {
198
+ self.showSidebar = arg
199
+ },
200
+ /**
201
+ * #action
202
+ */
203
+ setHighlightedRowNames(
204
+ names?: string[],
205
+ nodePosition?: { x: number; y: number },
206
+ ) {
207
+ self.highlightedRowNames = names
208
+ self.hoveredTreeNode = nodePosition
209
+ },
210
+ /**
211
+ * #action
212
+ */
213
+ showInsertionSequenceDialog(insertionData: {
214
+ sequence: string
215
+ sampleLabel: string
216
+ chr: string
217
+ pos: number
218
+ }) {
219
+ const { sequence, sampleLabel, chr, pos } = insertionData
220
+ const session = getSession(self) as SessionWithWidgets
221
+ const featureWidget = session.addWidget(
222
+ 'BaseFeatureWidget',
223
+ 'baseFeature',
224
+ {
225
+ featureData: {
226
+ uniqueId: `insertion-${chr}-${pos}-${sampleLabel}`,
227
+ type: 'insertion',
228
+ refName: chr,
229
+ start: pos,
230
+ end: pos + 1,
231
+ sample: sampleLabel,
232
+ insertionLength: sequence.length,
233
+ sequence: self.showAsUpperCase
234
+ ? sequence.toUpperCase()
235
+ : sequence.toLowerCase(),
236
+ },
237
+ view: getContainingView(self),
238
+ track: getContainingTrack(self),
239
+ },
240
+ )
241
+
242
+ session.showWidget(featureWidget)
243
+ },
153
244
  }))
154
245
  .views(self => ({
155
246
  /**
@@ -183,8 +274,7 @@ export default function stateModelFactory(
183
274
  get root() {
184
275
  return self.volatileTree
185
276
  ? hierarchy(self.volatileTree, d => d.children)
186
- // todo: investigate whether needed, typescript says children always true
187
- .sum(d => (d.children ? 0 : 1))
277
+ .sum(d => (d.children?.length ? 0 : 1))
188
278
  .sort((a, b) => ascending(a.data.length || 1, b.data.length || 1))
189
279
  : undefined
190
280
  },
@@ -198,11 +288,18 @@ export default function stateModelFactory(
198
288
  const r = self.root
199
289
  if (r) {
200
290
  const width = self.treeAreaWidth
291
+ // Use totalHeight - rowHeight so leaves are centered in rows
292
+ // (first leaf at rowHeight/2, last at totalHeight - rowHeight/2)
201
293
  const clust = cluster<NodeWithIds>()
202
- .size([this.totalHeight, width])
294
+ .size([this.totalHeight - self.rowHeight, width])
203
295
  .separation(() => 1)
204
296
  clust(r)
205
- setBrLength(r, (r.data.length = 0), width / maxLength(r))
297
+ // Offset all nodes by rowHeight/2 to center in rows
298
+ for (const node of r.descendants()) {
299
+ node.x = node.x! + self.rowHeight / 2
300
+ }
301
+ r.data.length = 0
302
+ setBrLength(r, 0, width / maxLength(r))
206
303
  return r as HierarchyNode<NodeWithIdsAndLength>
207
304
  } else {
208
305
  return undefined
@@ -238,6 +335,22 @@ export default function stateModelFactory(
238
335
  get leaves() {
239
336
  return self.root?.leaves()
240
337
  },
338
+ /**
339
+ * #getter
340
+ */
341
+ get leafMap() {
342
+ return new Map(this.leaves?.map(leaf => [leaf.data.name, leaf]))
343
+ },
344
+ /**
345
+ * #getter
346
+ * Precomputed map from hierarchy node to its descendant leaf names
347
+ */
348
+ get nodeDescendantNames() {
349
+ if (this.hierarchy) {
350
+ return computeNodeDescendantNames(this.hierarchy)
351
+ }
352
+ return new Map<HierarchyNode<NodeWithIdsAndLength>, string[]>()
353
+ },
241
354
  /**
242
355
  * #getter
243
356
  */
@@ -276,7 +389,7 @@ export default function stateModelFactory(
276
389
  return {
277
390
  ...s,
278
391
  notReady:
279
- (!self.volatileSamples && !self.volatileTree) || super.notReady,
392
+ (!self.volatileSamples && !self.volatileTree) || s.notReady,
280
393
  config: rendererConfig,
281
394
  samples,
282
395
  rowHeight,
@@ -348,11 +461,50 @@ export default function stateModelFactory(
348
461
  self.setMismatchRendering(!self.mismatchRendering)
349
462
  },
350
463
  },
464
+ {
465
+ label: 'Show sidebar',
466
+ type: 'checkbox',
467
+ checked: self.showSidebar,
468
+ onClick: () => {
469
+ self.setShowSidebar(!self.showSidebar)
470
+ },
471
+ },
351
472
  ]
352
473
  },
353
474
  }
354
475
  })
355
476
  .views(self => ({
477
+ /**
478
+ * #getter
479
+ * Get highlight regions from connected MSA views
480
+ */
481
+ get msaHighlights() {
482
+ const session = getSession(self)
483
+ const view = getContainingView(self)
484
+ const highlights: { refName: string; start: number; end: number }[] = []
485
+
486
+ // Find MSA views that are connected to our parent view
487
+ for (const v of session.views) {
488
+ if (
489
+ (v as { type?: string }).type === 'MsaView' &&
490
+ (v as { connectedViewId?: string }).connectedViewId === view.id
491
+ ) {
492
+ const msaView = v as {
493
+ connectedHighlights?: {
494
+ refName: string
495
+ start: number
496
+ end: number
497
+ }[]
498
+ }
499
+ if (msaView.connectedHighlights) {
500
+ for (const h of msaView.connectedHighlights) {
501
+ highlights.push(h)
502
+ }
503
+ }
504
+ }
505
+ }
506
+ return highlights
507
+ },
356
508
  /**
357
509
  * #getter
358
510
  */
@@ -377,6 +529,12 @@ export default function stateModelFactory(
377
529
  0,
378
530
  )
379
531
  },
532
+ /**
533
+ * #getter
534
+ */
535
+ get sidebarWidth() {
536
+ return self.showSidebar ? this.labelWidth + 5 + self.treeWidth : 0
537
+ },
380
538
  }))
381
539
  .actions(self => ({
382
540
  afterCreate() {
@@ -392,10 +550,10 @@ export default function stateModelFactory(
392
550
  adapterConfig: self.adapterConfig,
393
551
  statusCallback: (message: string) => {
394
552
  if (isAlive(self)) {
395
- self.setMessage(message)
553
+ self.setStatusMessage(message)
396
554
  }
397
555
  },
398
- })) as { samples: Sample[]; tree: unknown },
556
+ })) as { samples: Sample[]; tree: NodeWithIds | undefined },
399
557
  )
400
558
  } catch (e) {
401
559
  console.error(e)
@@ -418,6 +576,43 @@ export default function stateModelFactory(
418
576
  },
419
577
  }
420
578
  })
579
+ .postProcessSnapshot(snap => {
580
+ const {
581
+ rowHeight,
582
+ rowProportion,
583
+ showAllLetters,
584
+ mismatchRendering,
585
+ showBranchLen,
586
+ treeAreaWidth,
587
+ showAsUpperCase,
588
+ showSidebar,
589
+ ...rest
590
+ } = snap as typeof snap & {
591
+ rowHeight?: number
592
+ rowProportion?: number
593
+ showAllLetters?: boolean
594
+ mismatchRendering?: boolean
595
+ showBranchLen?: boolean
596
+ treeAreaWidth?: number
597
+ showAsUpperCase?: boolean
598
+ showSidebar?: boolean
599
+ }
600
+ return {
601
+ ...(rest as Omit<typeof rest, symbol>),
602
+ ...(rowHeight !== defaultRowHeight ? { rowHeight } : {}),
603
+ ...(rowProportion !== defaultRowProportion ? { rowProportion } : {}),
604
+ ...(showAllLetters !== defaultShowAllLetters ? { showAllLetters } : {}),
605
+ ...(mismatchRendering !== defaultMismatchRendering
606
+ ? { mismatchRendering }
607
+ : {}),
608
+ ...(showBranchLen !== defaultShowBranchLen ? { showBranchLen } : {}),
609
+ ...(treeAreaWidth !== defaultTreeAreaWidth ? { treeAreaWidth } : {}),
610
+ ...(showAsUpperCase !== defaultShowAsUpperCase
611
+ ? { showAsUpperCase }
612
+ : {}),
613
+ ...(showSidebar !== defaultShowSidebar ? { showSidebar } : {}),
614
+ }
615
+ })
421
616
  }
422
617
 
423
618
  export type LinearMafDisplayStateModel = ReturnType<typeof stateModelFactory>
@@ -1,7 +1,7 @@
1
1
  export interface NodeWithIds {
2
2
  id: string
3
3
  name: string
4
- children: NodeWithIds[]
4
+ children?: NodeWithIds[]
5
5
  length?: number
6
6
  noTree?: boolean
7
7
  }
@@ -9,7 +9,7 @@ export interface NodeWithIds {
9
9
  export interface NodeWithIdsAndLength {
10
10
  id: string
11
11
  name: string
12
- children: NodeWithIdsAndLength[]
12
+ children?: NodeWithIdsAndLength[]
13
13
  noTree?: boolean
14
14
  length: number
15
15
  }
@@ -6,10 +6,13 @@ import type { HierarchyNode } from 'd3-hierarchy'
6
6
 
7
7
  export interface HoveredInfo {
8
8
  sampleId: string
9
+ sampleLabel: string
9
10
  pos: number
10
11
  base: string
11
12
  chr: string
12
- [key: string]: unknown // Allow additional properties for compatibility
13
+ isInsertion?: boolean
14
+ isLargeInsertion?: boolean
15
+ [key: string]: unknown
13
16
  }
14
17
 
15
18
  export interface GenomicPosition {
@@ -44,15 +47,16 @@ export function generateTooltipContent(
44
47
  contentLines.push(`Ref: ${p2.refName}:${toLocale(p2.coord)}`)
45
48
 
46
49
  if (hoveredInfo) {
47
- const { base, sampleId, pos, chr } = hoveredInfo
50
+ const { base, sampleLabel, pos, chr, isInsertion } = hoveredInfo
48
51
  const thresh = 20
49
52
  const len = base.length
50
53
  const lengthSuffix = len > 1 ? ` ${len}bp` : ''
51
54
  const baseDisplay =
52
55
  base.length > thresh ? base.slice(0, thresh) + '...' : base
56
+ const insertionLabel = isInsertion ? ' Insertion' : ''
53
57
 
54
58
  contentLines.push(
55
- `Alt ${sampleId}: ${chr}:${pos.toLocaleString('en-US')} (${baseDisplay}${lengthSuffix})`,
59
+ `Alt ${sampleLabel}: ${chr}:${pos.toLocaleString('en-US')} (${baseDisplay}${lengthSuffix}${insertionLabel})`,
56
60
  )
57
61
  }
58
62
  }
@@ -73,12 +77,36 @@ export function setBrLength(
73
77
  y0: number,
74
78
  k: number,
75
79
  ) {
80
+ const newY0 = y0 + Math.max(d.data.length || 0, 0)
76
81
  // @ts-expect-error
77
- d.len = (y0 += Math.max(d.data.length || 0, 0)) * k
82
+ d.len = newY0 * k
78
83
 
79
84
  if (d.children) {
80
- d.children.forEach(d => {
81
- setBrLength(d, y0, k)
82
- })
85
+ for (const child of d.children) {
86
+ setBrLength(child, newY0, k)
87
+ }
88
+ }
89
+ }
90
+
91
+ export function computeNodeDescendantNames<T extends { name: string }>(
92
+ root: HierarchyNode<T>,
93
+ ): Map<HierarchyNode<T>, string[]> {
94
+ const map = new Map<HierarchyNode<T>, string[]>()
95
+ function visit(node: HierarchyNode<T>): string[] {
96
+ if (!node.children || node.children.length === 0) {
97
+ const names = [node.data.name]
98
+ map.set(node, names)
99
+ return names
100
+ }
101
+ const names: string[] = []
102
+ for (const child of node.children) {
103
+ for (const name of visit(child)) {
104
+ names.push(name)
105
+ }
106
+ }
107
+ map.set(node, names)
108
+ return names
83
109
  }
110
+ visit(root)
111
+ return map
84
112
  }
@@ -8,10 +8,8 @@ import {
8
8
 
9
9
  import { makeImageData } from './makeImageData'
10
10
 
11
- interface Sample {
12
- id: string
13
- color?: string
14
- }
11
+ import type { Sample } from '../LinearMafDisplay/types'
12
+
15
13
  interface RenderArgs extends RenderArgsDeserialized {
16
14
  samples: Sample[]
17
15
  rowHeight: number
@@ -28,7 +26,7 @@ export default class LinearMafRenderer extends FeatureRendererType {
28
26
  const bpExpansion = 1
29
27
 
30
28
  return {
31
- // xref https://github.com/mobxjs/mobx-state-tree/issues/1524 for Omit
29
+ // xref https://github.com/mobxjs/@jbrowse/mobx-state-tree/issues/1524 for Omit
32
30
  ...(region as Omit<typeof region, symbol>),
33
31
  start: Math.floor(Math.max(start - bpExpansion, 0)),
34
32
  end: Math.ceil(end + bpExpansion),
@@ -1,66 +1,100 @@
1
- import React, { useMemo, useRef } from 'react'
1
+ import React, { useCallback, useMemo, useRef, useState } from 'react'
2
2
 
3
3
  import { PrerenderedCanvas } from '@jbrowse/core/ui'
4
4
  import Flatbush from 'flatbush'
5
5
  import { observer } from 'mobx-react'
6
6
 
7
- import { Sample } from '../../LinearMafDisplay/types'
8
- import { RenderedBase } from '../rendering'
7
+ import type { Sample } from '../../LinearMafDisplay/types'
8
+ import type { RenderedBase } from '../rendering'
9
9
 
10
- type SerializedRBush = any
10
+ interface DisplayModel {
11
+ setHoveredInfo?: (info: Record<string, unknown> | undefined) => void
12
+ setHighlightedRowNames?: (names: string[] | undefined) => void
13
+ showInsertionSequenceDialog?: (data: {
14
+ sequence: string
15
+ sampleLabel: string
16
+ chr: string
17
+ pos: number
18
+ }) => void
19
+ }
11
20
 
12
21
  const LinearMafRendering = observer(function (props: {
13
22
  width: number
14
23
  height: number
15
- displayModel: any
16
- flatbush: SerializedRBush
24
+ displayModel: DisplayModel
25
+ flatbush: ArrayBuffer
17
26
  items: RenderedBase[]
18
27
  samples: Sample[]
19
28
  }) {
20
29
  const { items, displayModel, height, samples, flatbush } = props
21
30
  const ref = useRef<HTMLDivElement>(null)
22
- const rbush2 = useMemo(() => Flatbush.from(flatbush), [flatbush])
31
+ const flatbush2 = useMemo(() => Flatbush.from(flatbush), [flatbush])
32
+ const [isOverInsertion, setIsOverInsertion] = useState(false)
23
33
 
24
- function getFeatureUnderMouse(eventClientX: number, eventClientY: number) {
25
- let offsetX = 0
26
- let offsetY = 0
27
- if (ref.current) {
28
- const r = ref.current.getBoundingClientRect()
29
- offsetX = eventClientX - r.left
30
- offsetY = eventClientY - r.top
31
- }
34
+ const getFeatureUnderMouse = useCallback(
35
+ (eventClientX: number, eventClientY: number) => {
36
+ let offsetX = 0
37
+ let offsetY = 0
38
+ if (ref.current) {
39
+ const rect = ref.current.getBoundingClientRect()
40
+ offsetX = eventClientX - rect.left
41
+ offsetY = eventClientY - rect.top
42
+ }
43
+
44
+ const hits = flatbush2.search(offsetX, offsetY, offsetX + 1, offsetY + 1)
45
+ if (hits.length === 0) {
46
+ return undefined
47
+ }
32
48
 
33
- const x = rbush2.search(offsetX, offsetY, offsetX + 1, offsetY + 1)
34
- if (x.length) {
35
- const elt = x.find(idx => items[idx]?.isInsertion)
36
- const r = elt !== undefined ? items[elt]! : items[x[0]!]!
37
- const s = samples[r.sampleId]
49
+ const insertionHit = hits.find(idx => items[idx]?.isInsertion)
50
+ const hitIdx = insertionHit ?? hits[0]
51
+ const item = hitIdx !== undefined ? items[hitIdx] : undefined
52
+ if (!item) {
53
+ return undefined
54
+ }
55
+
56
+ const sample = samples[item.rowIndex]
38
57
  return {
39
- ...r,
40
- sampleId: s?.label || s?.id || 'unknown',
58
+ ...item,
59
+ sampleId: sample?.id ?? 'unknown',
60
+ sampleLabel: sample?.label || sample?.id || 'unknown',
41
61
  }
42
- } else {
43
- return undefined
44
- }
45
- }
62
+ },
63
+ [flatbush2, items, samples],
64
+ )
65
+
46
66
  return (
47
67
  <div
48
68
  ref={ref}
49
- onMouseMove={e =>
50
- displayModel.setHoveredInfo?.(
51
- getFeatureUnderMouse(e.clientX, e.clientY),
69
+ onClick={e => {
70
+ const feature = getFeatureUnderMouse(e.clientX, e.clientY)
71
+ if (feature?.isInsertion) {
72
+ displayModel.showInsertionSequenceDialog?.({
73
+ sequence: feature.base,
74
+ sampleLabel: feature.sampleLabel,
75
+ chr: feature.chr,
76
+ pos: feature.pos,
77
+ })
78
+ }
79
+ }}
80
+ onMouseMove={e => {
81
+ const feature = getFeatureUnderMouse(e.clientX, e.clientY)
82
+ displayModel.setHoveredInfo?.(feature)
83
+ displayModel.setHighlightedRowNames?.(
84
+ feature?.sampleId ? [feature.sampleId] : undefined,
52
85
  )
53
- }
54
- onMouseLeave={() => {
55
- displayModel.setHoveredInfo?.(undefined)
86
+ setIsOverInsertion(!!feature?.isInsertion)
56
87
  }}
57
- onMouseOut={() => {
88
+ onMouseLeave={() => {
58
89
  displayModel.setHoveredInfo?.(undefined)
90
+ displayModel.setHighlightedRowNames?.(undefined)
91
+ setIsOverInsertion(false)
59
92
  }}
60
93
  style={{
61
94
  overflow: 'visible',
62
95
  position: 'relative',
63
96
  height,
97
+ cursor: isOverInsertion ? 'pointer' : 'default',
64
98
  }}
65
99
  >
66
100
  <PrerenderedCanvas
@@ -7,12 +7,7 @@ function x() {} // eslint-disable-line @typescript-eslint/no-unused-vars
7
7
 
8
8
  const configSchema = ConfigurationSchema(
9
9
  'LinearMafRenderer',
10
- {
11
- baseColor: {
12
- type: 'color',
13
- defaultValue: 'lightgrey',
14
- },
15
- },
10
+ {},
16
11
  {
17
12
  /**
18
13
  * #baseConfiguration
@@ -8,9 +8,8 @@ import {
8
8
  RenderingContext,
9
9
  Sample,
10
10
  processFeatureAlignment,
11
- processFeatureInsertions,
12
11
  } from './rendering'
13
- import { getColorBaseMap, getContrastBaseMap } from './util'
12
+ import { getCharWidthHeight, getColorBaseMap, getContrastBaseMap } from './util'
14
13
 
15
14
  interface RenderArgs extends RenderArgsDeserialized {
16
15
  samples: Sample[]
@@ -53,12 +52,14 @@ export function makeImageData({
53
52
  const scale = 1 / bpPerPx
54
53
  const hp2 = h / 2
55
54
  const offset = (rowHeight - h) / 2
55
+ const { charWidth, charHeight } = getCharWidthHeight()
56
56
 
57
57
  ctx.font = FONT_CONFIG
58
58
 
59
59
  const renderingContext: RenderingContext = {
60
60
  ctx,
61
61
  scale,
62
+ bpPerPx,
62
63
  canvasWidth,
63
64
  rowHeight,
64
65
  h,
@@ -69,12 +70,13 @@ export function makeImageData({
69
70
  showAllLetters,
70
71
  mismatchRendering,
71
72
  showAsUpperCase,
73
+ charWidth,
74
+ charHeight,
72
75
  spatialIndex: [],
73
76
  spatialIndexCoords: [],
74
77
  lastInsertedX: -Infinity, // Start with -Infinity so first item is always inserted
75
78
  }
76
79
 
77
- // First pass: render alignments (gaps, matches, mismatches, text)
78
80
  for (const feature of features.values()) {
79
81
  processFeatureAlignment(
80
82
  feature,
@@ -84,17 +86,6 @@ export function makeImageData({
84
86
  renderingContext,
85
87
  )
86
88
  }
87
-
88
- // Second pass: render insertions on top
89
- for (const feature of features.values()) {
90
- processFeatureInsertions(
91
- feature,
92
- region,
93
- bpPerPx,
94
- sampleToRowMap,
95
- renderingContext,
96
- )
97
- }
98
89
  const flatbush = new Flatbush(renderingContext.spatialIndex.length || 1)
99
90
  if (renderingContext.spatialIndex.length === 0) {
100
91
  flatbush.add(0, 0, 1, 1)