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
@@ -0,0 +1,62 @@
1
+ import React from 'react'
2
+
3
+ import { observer } from 'mobx-react'
4
+
5
+ import type { LinearMafDisplayModel } from '../stateModel'
6
+ import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
7
+
8
+ const MsaHighlightOverlay = observer(function MsaHighlightOverlay({
9
+ model,
10
+ view,
11
+ height,
12
+ }: {
13
+ model: LinearMafDisplayModel
14
+ view: LinearGenomeViewModel
15
+ height: number
16
+ }) {
17
+ const { msaHighlights } = model
18
+ if (msaHighlights.length === 0) {
19
+ return null
20
+ }
21
+
22
+ const { offsetPx } = view
23
+ const displayedRegion = view.displayedRegions[0]
24
+ if (!displayedRegion) {
25
+ return null
26
+ }
27
+
28
+ return (
29
+ <>
30
+ {msaHighlights.map((highlight, idx) => {
31
+ // Check if highlight is on the displayed refName
32
+ if (highlight.refName !== displayedRegion.refName) {
33
+ return null
34
+ }
35
+
36
+ const startPx =
37
+ (highlight.start - displayedRegion.start) / view.bpPerPx - offsetPx
38
+ const endPx =
39
+ (highlight.end - displayedRegion.start) / view.bpPerPx - offsetPx
40
+ const widthPx = Math.max(endPx - startPx, 2)
41
+
42
+ return (
43
+ <div
44
+ key={idx}
45
+ style={{
46
+ position: 'absolute',
47
+ left: startPx,
48
+ top: 0,
49
+ width: widthPx,
50
+ height,
51
+ backgroundColor: 'rgba(255, 165, 0, 0.4)',
52
+ border: '1px solid rgba(255, 165, 0, 0.8)',
53
+ pointerEvents: 'none',
54
+ }}
55
+ />
56
+ )
57
+ })}
58
+ </>
59
+ )
60
+ })
61
+
62
+ export default MsaHighlightOverlay
@@ -17,6 +17,7 @@ const ColorLegend = observer(function ({
17
17
  canDisplayLabel,
18
18
  totalHeight,
19
19
  treeWidth,
20
+ sidebarWidth,
20
21
  samples = [],
21
22
  rowHeight,
22
23
  svgFontSize,
@@ -25,12 +26,7 @@ const ColorLegend = observer(function ({
25
26
 
26
27
  return (
27
28
  <>
28
- <RectBg
29
- y={0}
30
- x={0}
31
- width={labelWidth + 5 + treeWidth}
32
- height={totalHeight}
33
- />
29
+ <RectBg y={0} x={0} width={sidebarWidth} height={totalHeight} />
34
30
  <Tree model={model} />
35
31
  <g transform={`translate(${treeWidth + 5},0)`}>
36
32
  {samples.map((sample, idx) => (
@@ -1,14 +1,19 @@
1
1
  import React from 'react'
2
2
 
3
- const RectBg = (props: {
3
+ const RectBg = ({
4
+ x,
5
+ y,
6
+ width,
7
+ height,
8
+ color = 'rgb(255,255,255,0.5)',
9
+ }: {
4
10
  x: number
5
11
  y: number
6
12
  width: number
7
13
  height: number
8
14
  color?: string
9
15
  }) => {
10
- const { color = 'rgb(255,255,255,0.5)' } = props
11
- return <rect {...props} fill={color} />
16
+ return <rect x={x} y={y} width={width} height={height} fill={color} />
12
17
  }
13
18
 
14
19
  export default RectBg
@@ -1,11 +1,30 @@
1
- import React from 'react'
1
+ import React, { useEffect, useRef } from 'react'
2
2
 
3
+ import { ResizeHandle } from '@jbrowse/core/ui'
3
4
  import { getContainingView } from '@jbrowse/core/util'
5
+ import { isAlive } from '@jbrowse/mobx-state-tree'
6
+ import { autorun } from 'mobx'
4
7
  import { observer } from 'mobx-react'
8
+ import { makeStyles } from 'tss-react/mui'
5
9
 
6
10
  import type { LinearMafDisplayModel } from '../../stateModel'
7
11
  import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
8
12
 
13
+ const useStyles = makeStyles()({
14
+ resizeHandle: {
15
+ position: 'absolute',
16
+ top: 0,
17
+ height: '100%',
18
+ width: 4,
19
+ zIndex: 1001,
20
+ background: 'transparent',
21
+ cursor: 'col-resize',
22
+ '&:hover': {
23
+ background: 'rgba(0,0,0,0.2)',
24
+ },
25
+ },
26
+ })
27
+
9
28
  const SvgWrapper = observer(function ({
10
29
  children,
11
30
  model,
@@ -15,25 +34,108 @@ const SvgWrapper = observer(function ({
15
34
  children: React.ReactNode
16
35
  exportSVG?: boolean
17
36
  }) {
37
+ const { classes } = useStyles()
38
+ const mouseoverRef = useRef<HTMLCanvasElement>(null)
39
+
40
+ useEffect(() => {
41
+ const ctx = mouseoverRef.current?.getContext('2d')
42
+ return ctx
43
+ ? autorun(() => {
44
+ if (isAlive(model)) {
45
+ const {
46
+ totalHeight,
47
+ leafMap,
48
+ rowHeight,
49
+ highlightedRowNames,
50
+ hoveredTreeNode,
51
+ } = model
52
+ const { width: viewWidth } = getContainingView(
53
+ model,
54
+ ) as LinearGenomeViewModel
55
+
56
+ ctx.resetTransform()
57
+ ctx.clearRect(0, 0, viewWidth, totalHeight)
58
+
59
+ if (highlightedRowNames) {
60
+ ctx.fillStyle = 'rgba(255,165,0,0.2)'
61
+ const halfRowHeight = rowHeight / 2
62
+ for (const name of highlightedRowNames) {
63
+ const leaf = leafMap.get(name)
64
+ if (leaf) {
65
+ ctx.fillRect(0, leaf.x! - halfRowHeight, viewWidth, rowHeight)
66
+ }
67
+ }
68
+
69
+ // Draw orange dot at hovered tree node
70
+ if (hoveredTreeNode) {
71
+ ctx.fillStyle = 'rgba(255,165,0,0.8)'
72
+ ctx.beginPath()
73
+ ctx.arc(hoveredTreeNode.y, hoveredTreeNode.x, 4, 0, 2 * Math.PI)
74
+ ctx.fill()
75
+ ctx.strokeStyle = 'rgba(255,140,0,1)'
76
+ ctx.lineWidth = 1
77
+ ctx.stroke()
78
+ }
79
+ }
80
+ }
81
+ })
82
+ : undefined
83
+ }, [model])
84
+
18
85
  if (exportSVG) {
19
86
  return <>{children}</>
20
87
  } else {
21
- const { totalHeight } = model
88
+ const { totalHeight, treeWidth, hierarchy } = model
22
89
  const { width } = getContainingView(model) as LinearGenomeViewModel
23
90
  return (
24
- <svg
25
- style={{
26
- position: 'absolute',
27
- userSelect: 'none',
28
- top: 0,
29
- left: 0,
30
- pointerEvents: 'none',
31
- height: totalHeight,
32
- width,
33
- }}
34
- >
35
- {children}
36
- </svg>
91
+ <>
92
+ <svg
93
+ style={{
94
+ position: 'absolute',
95
+ userSelect: 'none',
96
+ top: 0,
97
+ left: 0,
98
+ pointerEvents: 'none',
99
+ height: totalHeight,
100
+ width,
101
+ }}
102
+ >
103
+ {children}
104
+ </svg>
105
+ <canvas
106
+ ref={mouseoverRef}
107
+ width={width}
108
+ height={totalHeight}
109
+ style={{
110
+ position: 'absolute',
111
+ top: 0,
112
+ left: 0,
113
+ width,
114
+ height: totalHeight,
115
+ zIndex: 1000,
116
+ pointerEvents: 'none',
117
+ }}
118
+ />
119
+ {hierarchy ? (
120
+ <div
121
+ onMouseDown={e => {
122
+ e.stopPropagation()
123
+ }}
124
+ >
125
+ <ResizeHandle
126
+ onDrag={distance => {
127
+ model.setTreeAreaWidth(
128
+ Math.max(20, model.treeAreaWidth + distance),
129
+ )
130
+ return undefined
131
+ }}
132
+ className={classes.resizeHandle}
133
+ style={{ left: treeWidth }}
134
+ vertical
135
+ />
136
+ </div>
137
+ ) : null}
138
+ </>
37
139
  )
38
140
  }
39
141
  })
@@ -1,20 +1,48 @@
1
- import React from 'react'
1
+ import React, { useCallback, useMemo } from 'react'
2
2
 
3
3
  import { observer } from 'mobx-react'
4
4
 
5
5
  import type { LinearMafDisplayModel } from '../../stateModel'
6
+ import type { NodeWithIdsAndLength } from '../../types'
7
+ import type { HierarchyNode } from 'd3-hierarchy'
8
+
9
+ const hitboxStyle = {
10
+ pointerEvents: 'all',
11
+ cursor: 'pointer',
12
+ strokeWidth: 8,
13
+ stroke: 'transparent',
14
+ } as const
6
15
 
7
16
  const Tree = observer(function ({ model }: { model: LinearMafDisplayModel }) {
8
17
  const {
9
- // this is needed for redrawing after zoom change, similar to react-msaview
10
- // renderTreeCanvas
18
+ // rowHeight is needed for redrawing after zoom change
11
19
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
12
20
  rowHeight: _rowHeight,
13
-
21
+ treeAreaWidth,
14
22
  hierarchy,
15
23
  showBranchLen,
24
+ nodeDescendantNames,
16
25
  } = model
17
26
 
27
+ const clearHighlight = useCallback(() => {
28
+ model.setHighlightedRowNames(undefined)
29
+ }, [model])
30
+
31
+ const nodeHandlers = useMemo(() => {
32
+ const handlers = new Map<HierarchyNode<NodeWithIdsAndLength>, () => void>()
33
+ if (hierarchy) {
34
+ for (const node of hierarchy.descendants()) {
35
+ handlers.set(node, () => {
36
+ model.setHighlightedRowNames(nodeDescendantNames.get(node), {
37
+ x: node.x!,
38
+ y: node.y!,
39
+ })
40
+ })
41
+ }
42
+ }
43
+ return handlers
44
+ }, [model, hierarchy, nodeDescendantNames, treeAreaWidth])
45
+
18
46
  return (
19
47
  <>
20
48
  {hierarchy
@@ -27,13 +55,30 @@ const Tree = observer(function ({ model }: { model: LinearMafDisplayModel }) {
27
55
  // @ts-expect-error
28
56
  const sx = showBranchLen ? source.len : source.y
29
57
 
30
- // 1d line intersection to check if line crosses block at all, this is
31
- // an optimization that allows us to skip drawing most tree links
32
- // outside the block
33
58
  return (
34
- <React.Fragment key={[sy, ty, tx, sx].join('-')}>
59
+ <React.Fragment key={`${treeAreaWidth}-${sy}-${ty}-${tx}-${sx}`}>
60
+ {/* Visible lines */}
35
61
  <line stroke="black" x1={sx} y1={sy} x2={sx} y2={ty} />
36
62
  <line stroke="black" x1={sx} y1={ty} x2={tx} y2={ty} />
63
+ {/* Invisible hitbox lines */}
64
+ <line
65
+ x1={sx}
66
+ y1={sy}
67
+ x2={sx}
68
+ y2={ty}
69
+ style={hitboxStyle}
70
+ onMouseEnter={nodeHandlers.get(source)}
71
+ onMouseLeave={clearHighlight}
72
+ />
73
+ <line
74
+ x1={sx}
75
+ y1={ty}
76
+ x2={tx}
77
+ y2={ty}
78
+ style={hitboxStyle}
79
+ onMouseEnter={nodeHandlers.get(target)}
80
+ onMouseLeave={clearHighlight}
81
+ />
37
82
  </React.Fragment>
38
83
  )
39
84
  })
@@ -9,7 +9,6 @@ import type { LinearMafDisplayModel } from '../../stateModel'
9
9
 
10
10
  export const YScaleBars = observer(function (props: {
11
11
  model: LinearMafDisplayModel
12
- orientation?: string
13
12
  exportSVG?: boolean
14
13
  }) {
15
14
  const { model } = props
@@ -0,0 +1,159 @@
1
+ import { useCallback, useEffect, useState } from 'react'
2
+
3
+ const MIN_DRAG_DISTANCE = 3
4
+
5
+ interface DragSelectionState {
6
+ isDragging: boolean
7
+ dragStartX: number | undefined
8
+ dragEndX: number | undefined
9
+ showSelectionBox: boolean
10
+ mouseX: number | undefined
11
+ mouseY: number | undefined
12
+ }
13
+
14
+ interface DragSelectionHandlers {
15
+ handleMouseDown: (event: React.MouseEvent) => void
16
+ handleMouseMove: (event: React.MouseEvent) => void
17
+ handleMouseUp: (event: React.MouseEvent) => void
18
+ handleMouseLeave: () => void
19
+ clearSelectionBox: () => void
20
+ }
21
+
22
+ interface ContextCoord {
23
+ coord: [number, number]
24
+ dragStartX: number
25
+ dragEndX: number
26
+ }
27
+
28
+ export function useDragSelection(
29
+ ref: React.RefObject<HTMLDivElement | null>,
30
+ ): DragSelectionState &
31
+ DragSelectionHandlers & {
32
+ contextCoord: ContextCoord | undefined
33
+ setContextCoord: (coord: ContextCoord | undefined) => void
34
+ } {
35
+ const [isDragging, setIsDragging] = useState(false)
36
+ const [dragStartX, setDragStartX] = useState<number>()
37
+ const [dragEndX, setDragEndX] = useState<number>()
38
+ const [showSelectionBox, setShowSelectionBox] = useState(false)
39
+ const [mouseX, setMouseX] = useState<number>()
40
+ const [mouseY, setMouseY] = useState<number>()
41
+ const [contextCoord, setContextCoord] = useState<ContextCoord>()
42
+
43
+ const clearSelectionBox = useCallback(() => {
44
+ setShowSelectionBox(false)
45
+ setDragStartX(undefined)
46
+ setDragEndX(undefined)
47
+ }, [])
48
+
49
+ const handleMouseDown = useCallback(
50
+ (event: React.MouseEvent) => {
51
+ if (event.shiftKey) {
52
+ return
53
+ }
54
+ const rect = ref.current?.getBoundingClientRect()
55
+ const left = rect?.left || 0
56
+ const clientX = event.clientX - left
57
+
58
+ setShowSelectionBox(false)
59
+ setIsDragging(true)
60
+ setDragStartX(clientX)
61
+ setDragEndX(clientX)
62
+ event.stopPropagation()
63
+ },
64
+ [ref],
65
+ )
66
+
67
+ const handleMouseMove = useCallback(
68
+ (event: React.MouseEvent) => {
69
+ const rect = ref.current?.getBoundingClientRect()
70
+ const top = rect?.top || 0
71
+ const left = rect?.left || 0
72
+ const clientX = event.clientX - left
73
+ const clientY = event.clientY - top
74
+
75
+ setMouseY(clientY)
76
+ setMouseX(clientX)
77
+
78
+ if (isDragging) {
79
+ setDragEndX(clientX)
80
+ }
81
+ },
82
+ [ref, isDragging],
83
+ )
84
+
85
+ const handleMouseUp = useCallback(
86
+ (event: React.MouseEvent) => {
87
+ if (isDragging && dragStartX !== undefined && dragEndX !== undefined) {
88
+ const dragDistanceX = Math.abs(dragEndX - dragStartX)
89
+
90
+ if (dragDistanceX > MIN_DRAG_DISTANCE) {
91
+ setContextCoord({
92
+ coord: [event.clientX, event.clientY],
93
+ dragEndX: event.clientX,
94
+ dragStartX: dragStartX,
95
+ })
96
+ setShowSelectionBox(true)
97
+ } else {
98
+ clearSelectionBox()
99
+ }
100
+ }
101
+ setIsDragging(false)
102
+ },
103
+ [isDragging, dragStartX, dragEndX, clearSelectionBox],
104
+ )
105
+
106
+ const handleMouseLeave = useCallback(() => {
107
+ setMouseY(undefined)
108
+ setMouseX(undefined)
109
+ setIsDragging(false)
110
+ }, [])
111
+
112
+ useEffect(() => {
113
+ const handleKeyDown = (event: KeyboardEvent) => {
114
+ if (event.key === 'Escape' && showSelectionBox) {
115
+ clearSelectionBox()
116
+ }
117
+ }
118
+
119
+ const handleClickOutside = (event: MouseEvent) => {
120
+ if (
121
+ ref.current &&
122
+ !ref.current.contains(event.target as Node) &&
123
+ showSelectionBox
124
+ ) {
125
+ clearSelectionBox()
126
+ }
127
+ }
128
+
129
+ document.addEventListener('keydown', handleKeyDown)
130
+ document.addEventListener('click', handleClickOutside)
131
+
132
+ return () => {
133
+ document.removeEventListener('keydown', handleKeyDown)
134
+ document.removeEventListener('click', handleClickOutside)
135
+ }
136
+ }, [ref, showSelectionBox, clearSelectionBox])
137
+
138
+ const dragDistance =
139
+ dragStartX !== undefined && dragEndX !== undefined
140
+ ? Math.abs(dragEndX - dragStartX)
141
+ : 0
142
+ const hasDraggedEnough = dragDistance > MIN_DRAG_DISTANCE
143
+
144
+ return {
145
+ isDragging: isDragging && hasDraggedEnough,
146
+ dragStartX,
147
+ dragEndX,
148
+ showSelectionBox,
149
+ mouseX,
150
+ mouseY,
151
+ contextCoord,
152
+ setContextCoord,
153
+ handleMouseDown,
154
+ handleMouseMove,
155
+ handleMouseUp,
156
+ handleMouseLeave,
157
+ clearSelectionBox,
158
+ }
159
+ }
@@ -28,7 +28,7 @@ export async function renderSvg(
28
28
  <g clipPath={`url(#${clipid})`}>
29
29
  <g id="snpcov">{await superRenderSvg(opts)}</g>
30
30
  <g transform={`translate(${Math.max(-offsetPx, 0)})`}>
31
- <YScaleBars model={self} orientation="left" exportSVG />
31
+ <YScaleBars model={self} exportSVG />
32
32
  </g>
33
33
  </g>
34
34
  </>