jbrowse-plugin-mafviewer 1.2.4 → 1.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 (112) hide show
  1. package/dist/LinearMafDisplay/components/Crosshairs.d.ts +10 -0
  2. package/dist/LinearMafDisplay/components/Crosshairs.js +18 -0
  3. package/dist/LinearMafDisplay/components/Crosshairs.js.map +1 -0
  4. package/dist/LinearMafDisplay/components/GetSequenceDialog/GetSequenceDialog.d.ts +11 -0
  5. package/dist/LinearMafDisplay/components/GetSequenceDialog/GetSequenceDialog.js +97 -0
  6. package/dist/LinearMafDisplay/components/GetSequenceDialog/GetSequenceDialog.js.map +1 -0
  7. package/dist/LinearMafDisplay/components/LinearMafDisplayComponent.js +147 -29
  8. package/dist/LinearMafDisplay/components/LinearMafDisplayComponent.js.map +1 -1
  9. package/dist/LinearMafDisplay/components/MAFTooltip.d.ts +12 -0
  10. package/dist/LinearMafDisplay/components/MAFTooltip.js +29 -0
  11. package/dist/LinearMafDisplay/components/MAFTooltip.js.map +1 -0
  12. package/dist/LinearMafDisplay/components/SetRowHeightDialog/SetRowHeightDialog.js.map +1 -0
  13. package/dist/LinearMafDisplay/components/{ColorLegend.d.ts → Sidebar/ColorLegend.d.ts} +1 -1
  14. package/dist/LinearMafDisplay/components/Sidebar/ColorLegend.js.map +1 -0
  15. package/dist/LinearMafDisplay/components/Sidebar/RectBg.js.map +1 -0
  16. package/dist/LinearMafDisplay/components/{SvgWrapper.d.ts → Sidebar/SvgWrapper.d.ts} +1 -1
  17. package/dist/LinearMafDisplay/components/{SvgWrapper.js → Sidebar/SvgWrapper.js} +3 -1
  18. package/dist/LinearMafDisplay/components/Sidebar/SvgWrapper.js.map +1 -0
  19. package/dist/LinearMafDisplay/components/{Tree.d.ts → Sidebar/Tree.d.ts} +1 -1
  20. package/dist/LinearMafDisplay/components/Sidebar/Tree.js.map +1 -0
  21. package/dist/LinearMafDisplay/components/{YScaleBars.d.ts → Sidebar/YScaleBars.d.ts} +1 -1
  22. package/dist/LinearMafDisplay/components/Sidebar/YScaleBars.js.map +1 -0
  23. package/dist/LinearMafDisplay/renderSvg.js +1 -1
  24. package/dist/LinearMafDisplay/renderSvg.js.map +1 -1
  25. package/dist/LinearMafDisplay/stateModel.d.ts +8 -6
  26. package/dist/LinearMafDisplay/stateModel.js +21 -2
  27. package/dist/LinearMafDisplay/stateModel.js.map +1 -1
  28. package/dist/LinearMafDisplay/types.d.ts +1 -1
  29. package/dist/LinearMafRenderer/LinearMafRenderer.d.ts +1 -0
  30. package/dist/LinearMafRenderer/LinearMafRenderer.js.map +1 -1
  31. package/dist/LinearMafRenderer/makeImageData.d.ts +1 -0
  32. package/dist/LinearMafRenderer/makeImageData.js +7 -4
  33. package/dist/LinearMafRenderer/makeImageData.js.map +1 -1
  34. package/dist/{MafRPC/index.d.ts → MafGetSamples/MafGetSamples.d.ts} +1 -3
  35. package/dist/{MafRPC/index.js → MafGetSamples/MafGetSamples.js} +2 -7
  36. package/dist/MafGetSamples/MafGetSamples.js.map +1 -0
  37. package/dist/MafGetSamples/index.d.ts +2 -0
  38. package/dist/MafGetSamples/index.js +7 -0
  39. package/dist/MafGetSamples/index.js.map +1 -0
  40. package/dist/MafGetSequences/MafGetSequences.d.ts +16 -0
  41. package/dist/MafGetSequences/MafGetSequences.js +20 -0
  42. package/dist/MafGetSequences/MafGetSequences.js.map +1 -0
  43. package/dist/MafGetSequences/index.d.ts +2 -0
  44. package/dist/MafGetSequences/index.js +7 -0
  45. package/dist/MafGetSequences/index.js.map +1 -0
  46. package/dist/MafTabixAdapter/MafTabixAdapter.js +3 -4
  47. package/dist/MafTabixAdapter/MafTabixAdapter.js.map +1 -1
  48. package/dist/index.js +4 -2
  49. package/dist/index.js.map +1 -1
  50. package/dist/jbrowse-plugin-mafviewer.umd.production.min.js +9 -7
  51. package/dist/jbrowse-plugin-mafviewer.umd.production.min.js.map +4 -4
  52. package/dist/util/extractSubsequence.d.ts +12 -0
  53. package/dist/util/extractSubsequence.js +60 -0
  54. package/dist/util/extractSubsequence.js.map +1 -0
  55. package/dist/util/extractSubsequence.test.d.ts +1 -0
  56. package/dist/util/extractSubsequence.test.js +42 -0
  57. package/dist/util/extractSubsequence.test.js.map +1 -0
  58. package/dist/util/fastaUtils.d.ts +16 -0
  59. package/dist/util/fastaUtils.js +84 -0
  60. package/dist/util/fastaUtils.js.map +1 -0
  61. package/dist/util/fastaUtils.test.d.ts +1 -0
  62. package/dist/util/fastaUtils.test.js +95 -0
  63. package/dist/util/fastaUtils.test.js.map +1 -0
  64. package/dist/util/fetchSequences.d.ts +18 -0
  65. package/dist/util/fetchSequences.js +39 -0
  66. package/dist/util/fetchSequences.js.map +1 -0
  67. package/dist/util/useSequences.d.ts +21 -0
  68. package/dist/util/useSequences.js +64 -0
  69. package/dist/util/useSequences.js.map +1 -0
  70. package/package.json +5 -5
  71. package/src/LinearMafDisplay/components/Crosshairs.tsx +50 -0
  72. package/src/LinearMafDisplay/components/GetSequenceDialog/GetSequenceDialog.tsx +175 -0
  73. package/src/LinearMafDisplay/components/LinearMafDisplayComponent.tsx +211 -45
  74. package/src/LinearMafDisplay/components/MAFTooltip.tsx +59 -0
  75. package/src/LinearMafDisplay/components/{ColorLegend.tsx → Sidebar/ColorLegend.tsx} +1 -1
  76. package/src/LinearMafDisplay/components/{SvgWrapper.tsx → Sidebar/SvgWrapper.tsx} +5 -2
  77. package/src/LinearMafDisplay/components/{Tree.tsx → Sidebar/Tree.tsx} +1 -1
  78. package/src/LinearMafDisplay/components/{YScaleBars.tsx → Sidebar/YScaleBars.tsx} +1 -1
  79. package/src/LinearMafDisplay/renderSvg.tsx +1 -1
  80. package/src/LinearMafDisplay/stateModel.ts +23 -1
  81. package/src/LinearMafDisplay/types.ts +1 -1
  82. package/src/LinearMafRenderer/LinearMafRenderer.ts +1 -0
  83. package/src/LinearMafRenderer/makeImageData.ts +15 -5
  84. package/src/{MafRPC/index.ts → MafGetSamples/MafGetSamples.ts} +1 -8
  85. package/src/MafGetSamples/index.ts +9 -0
  86. package/src/MafGetSequences/MafGetSequences.ts +47 -0
  87. package/src/MafGetSequences/index.ts +9 -0
  88. package/src/MafTabixAdapter/MafTabixAdapter.ts +4 -5
  89. package/src/index.ts +4 -2
  90. package/src/util/__snapshots__/fastaUtils.test.ts.snap +22 -0
  91. package/src/util/extractSubsequence.test.ts +54 -0
  92. package/src/util/extractSubsequence.ts +74 -0
  93. package/src/util/fastaUtils.test.ts +99 -0
  94. package/src/util/fastaUtils.ts +102 -0
  95. package/src/util/fetchSequences.ts +57 -0
  96. package/src/util/useSequences.ts +90 -0
  97. package/dist/LinearMafDisplay/components/ColorLegend.js.map +0 -1
  98. package/dist/LinearMafDisplay/components/RectBg.js.map +0 -1
  99. package/dist/LinearMafDisplay/components/SetRowHeightDialog.js.map +0 -1
  100. package/dist/LinearMafDisplay/components/SvgWrapper.js.map +0 -1
  101. package/dist/LinearMafDisplay/components/Tree.js.map +0 -1
  102. package/dist/LinearMafDisplay/components/YScaleBars.js.map +0 -1
  103. package/dist/MafRPC/index.js.map +0 -1
  104. /package/dist/LinearMafDisplay/components/{SetRowHeightDialog.d.ts → SetRowHeightDialog/SetRowHeightDialog.d.ts} +0 -0
  105. /package/dist/LinearMafDisplay/components/{SetRowHeightDialog.js → SetRowHeightDialog/SetRowHeightDialog.js} +0 -0
  106. /package/dist/LinearMafDisplay/components/{ColorLegend.js → Sidebar/ColorLegend.js} +0 -0
  107. /package/dist/LinearMafDisplay/components/{RectBg.d.ts → Sidebar/RectBg.d.ts} +0 -0
  108. /package/dist/LinearMafDisplay/components/{RectBg.js → Sidebar/RectBg.js} +0 -0
  109. /package/dist/LinearMafDisplay/components/{Tree.js → Sidebar/Tree.js} +0 -0
  110. /package/dist/LinearMafDisplay/components/{YScaleBars.js → Sidebar/YScaleBars.js} +0 -0
  111. /package/src/LinearMafDisplay/components/{SetRowHeightDialog.tsx → SetRowHeightDialog/SetRowHeightDialog.tsx} +0 -0
  112. /package/src/LinearMafDisplay/components/{RectBg.tsx → Sidebar/RectBg.tsx} +0 -0
@@ -0,0 +1,175 @@
1
+ import React, { useState } from 'react'
2
+
3
+ import { Dialog, ErrorMessage, LoadingEllipses } from '@jbrowse/core/ui'
4
+ import { getSession } from '@jbrowse/core/util'
5
+ import {
6
+ Button,
7
+ DialogActions,
8
+ DialogContent,
9
+ TextField,
10
+ ToggleButton,
11
+ ToggleButtonGroup,
12
+ } from '@mui/material'
13
+ import { observer } from 'mobx-react'
14
+ import { makeStyles } from 'tss-react/mui'
15
+
16
+ import { useSequences } from '../../../util/useSequences'
17
+
18
+ import type { LinearMafDisplayModel } from '../../stateModel'
19
+
20
+ const useStyles = makeStyles()({
21
+ dialogContent: {
22
+ width: '80em',
23
+ },
24
+ textAreaInput: {
25
+ fontFamily: 'monospace',
26
+ whiteSpace: 'pre',
27
+ overflowX: 'auto',
28
+ },
29
+ ml: {
30
+ marginLeft: 10,
31
+ },
32
+ })
33
+
34
+ const GetSequenceDialog = observer(function ({
35
+ onClose,
36
+ model,
37
+ selectionCoords,
38
+ }: {
39
+ onClose: () => void
40
+ model: LinearMafDisplayModel
41
+ selectionCoords?: {
42
+ dragStartX: number
43
+ dragEndX: number
44
+ }
45
+ }) {
46
+ const [showAllLetters, setShowAllLetters] = useState(true)
47
+ const { classes } = useStyles()
48
+ const { sequence, loading, error } = useSequences({
49
+ model,
50
+ selectionCoords,
51
+ showAllLetters,
52
+ })
53
+ const sequenceTooLarge = sequence ? sequence.length > 1_000_000 : false
54
+
55
+ return (
56
+ <Dialog open onClose={onClose} title="Subsequence Data" maxWidth="xl">
57
+ <DialogContent>
58
+ <div
59
+ style={{
60
+ display: 'flex',
61
+ alignItems: 'center',
62
+ marginBottom: '16px',
63
+ }}
64
+ >
65
+ <ToggleButtonGroup
66
+ value={showAllLetters}
67
+ exclusive
68
+ size="small"
69
+ onChange={(_event, newDisplayMode) => {
70
+ if (newDisplayMode !== null) {
71
+ setShowAllLetters(newDisplayMode)
72
+ }
73
+ }}
74
+ >
75
+ <ToggleButton value={true}>Show All Letters</ToggleButton>
76
+ <ToggleButton value={false}>Show Only Differences</ToggleButton>
77
+ </ToggleButtonGroup>
78
+ <div style={{ flexGrow: 1 }} />
79
+ <Button
80
+ variant="contained"
81
+ color="primary"
82
+ disabled={loading || !sequence}
83
+ onClick={() => {
84
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
85
+ ;(async () => {
86
+ try {
87
+ await navigator.clipboard.writeText(sequence)
88
+ getSession(model).notify(
89
+ 'Sequence copied to clipboard',
90
+ 'info',
91
+ )
92
+ } catch (e) {
93
+ console.error(e)
94
+ getSession(model).notifyError(`${e}`, e)
95
+ }
96
+ })()
97
+ }}
98
+ >
99
+ Copy to Clipboard
100
+ </Button>
101
+ <Button
102
+ variant="contained"
103
+ color="secondary"
104
+ disabled={loading || !sequence}
105
+ onClick={() => {
106
+ try {
107
+ const url = URL.createObjectURL(
108
+ new Blob([sequence], { type: 'text/plain' }),
109
+ )
110
+
111
+ // Create a temporary anchor element
112
+ const a = document.createElement('a')
113
+ a.href = url
114
+ a.download = 'sequence.fasta'
115
+
116
+ // Trigger the download
117
+ document.body.append(a)
118
+ a.click()
119
+
120
+ // Clean up
121
+ a.remove()
122
+ URL.revokeObjectURL(url)
123
+ getSession(model).notify('Sequence downloaded', 'info')
124
+ } catch (e) {
125
+ console.error(e)
126
+ getSession(model).notifyError(`${e}`, e)
127
+ }
128
+ }}
129
+ >
130
+ Download
131
+ </Button>
132
+ </div>
133
+
134
+ {error ? (
135
+ <ErrorMessage error={error} />
136
+ ) : (
137
+ <>
138
+ {loading ? <LoadingEllipses /> : null}
139
+ <TextField
140
+ variant="outlined"
141
+ multiline
142
+ minRows={5}
143
+ maxRows={10}
144
+ disabled={sequenceTooLarge}
145
+ className={classes.dialogContent}
146
+ fullWidth
147
+ value={
148
+ loading
149
+ ? 'Loading...'
150
+ : sequenceTooLarge
151
+ ? 'Reference sequence too large to display, use the download FASTA button'
152
+ : sequence
153
+ }
154
+ slotProps={{
155
+ input: {
156
+ readOnly: true,
157
+ classes: {
158
+ input: classes.textAreaInput,
159
+ },
160
+ },
161
+ }}
162
+ />
163
+ </>
164
+ )}
165
+ </DialogContent>
166
+ <DialogActions>
167
+ <Button color="primary" variant="outlined" onClick={onClose}>
168
+ Close
169
+ </Button>
170
+ </DialogActions>
171
+ </Dialog>
172
+ )
173
+ })
174
+
175
+ export default GetSequenceDialog
@@ -1,30 +1,26 @@
1
- import React, { useRef, useState } from 'react'
1
+ import React, { useEffect, useRef, useState } from 'react'
2
2
 
3
- import { SanitizedHTML } from '@jbrowse/core/ui'
4
- import BaseTooltip from '@jbrowse/core/ui/BaseTooltip'
3
+ import { Menu } from '@jbrowse/core/ui'
5
4
  import { getContainingView, getEnv } from '@jbrowse/core/util'
5
+ import { useTheme } from '@mui/material'
6
6
  import { observer } from 'mobx-react'
7
- import { makeStyles } from 'tss-react/mui'
8
7
 
9
- import YScaleBars from './YScaleBars'
8
+ import Crosshairs from './Crosshairs'
9
+ import SequenceDialog from './GetSequenceDialog/GetSequenceDialog'
10
+ import MAFTooltip from './MAFTooltip'
11
+ import YScaleBars from './Sidebar/YScaleBars'
10
12
 
11
13
  import type { LinearMafDisplayModel } from '../stateModel'
12
14
  import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
13
15
 
14
- const useStyles = makeStyles()({
15
- cursor: {
16
- pointerEvents: 'none',
17
- },
18
- })
19
-
20
16
  const LinearMafDisplay = observer(function (props: {
21
17
  model: LinearMafDisplayModel
22
18
  }) {
23
19
  const { model } = props
24
- const { classes } = useStyles()
25
20
  const { pluginManager } = getEnv(model)
26
21
  const { rowHeight, height, scrollTop, samples: sources } = model
27
22
  const ref = useRef<HTMLDivElement>(null)
23
+ const theme = useTheme()
28
24
 
29
25
  const LinearGenomePlugin = pluginManager.getPlugin(
30
26
  'LinearGenomeViewPlugin',
@@ -33,57 +29,227 @@ const LinearMafDisplay = observer(function (props: {
33
29
 
34
30
  const [mouseY, setMouseY] = useState<number>()
35
31
  const [mouseX, setMouseX] = useState<number>()
32
+ const [isDragging, setIsDragging] = useState(false)
33
+ const [dragStartX, setDragStartX] = useState<number>()
34
+ const [dragEndX, setDragEndX] = useState<number>()
35
+ const [showSelectionBox, setShowSelectionBox] = useState(false)
36
+ const [contextCoord, setContextCoord] = useState<{
37
+ coord: [number, number]
38
+ dragStartX: number
39
+ dragEndX: number
40
+ }>()
41
+ const [showSequenceDialog, setShowSequenceDialog] = useState(false)
42
+ const [selectionCoords, setSelectionCoords] = useState<
43
+ | {
44
+ dragStartX: number
45
+ dragEndX: number
46
+ }
47
+ | undefined
48
+ >()
36
49
  const { width } = getContainingView(model) as LinearGenomeViewModel
37
50
 
51
+ const handleMouseDown = (event: React.MouseEvent) => {
52
+ const rect = ref.current?.getBoundingClientRect()
53
+ const left = rect?.left || 0
54
+ const clientX = event.clientX - left
55
+
56
+ // Clear the previous selection box when starting a new drag
57
+ setShowSelectionBox(false)
58
+ setIsDragging(true)
59
+ setDragStartX(clientX)
60
+ setDragEndX(clientX)
61
+ event.stopPropagation()
62
+ }
63
+
64
+ const handleMouseMove = (event: React.MouseEvent) => {
65
+ const rect = ref.current?.getBoundingClientRect()
66
+ const top = rect?.top || 0
67
+ const left = rect?.left || 0
68
+ const clientX = event.clientX - left
69
+ const clientY = event.clientY - top
70
+
71
+ setMouseY(clientY)
72
+ setMouseX(clientX)
73
+
74
+ if (isDragging) {
75
+ setDragEndX(clientX)
76
+ }
77
+ }
78
+
79
+ const handleMouseUp = (event: React.MouseEvent) => {
80
+ if (isDragging && dragStartX !== undefined && dragEndX !== undefined) {
81
+ // Calculate the drag distance
82
+ const dragDistanceX = Math.abs(dragEndX - dragStartX)
83
+
84
+ // Only show context menu if the drag distance is at least 2 pixels in either direction
85
+ if (dragDistanceX >= 2) {
86
+ setContextCoord({
87
+ coord: [event.clientX, event.clientY],
88
+ dragEndX: event.clientX,
89
+ dragStartX: dragStartX,
90
+ })
91
+
92
+ // Set showSelectionBox to true to keep the selection visible
93
+ setShowSelectionBox(true)
94
+ } else {
95
+ // For very small drags (less than 2px), don't show selection box or context menu
96
+ clearSelectionBox()
97
+ }
98
+ }
99
+
100
+ // Only set isDragging to false, but keep the coordinates
101
+ setIsDragging(false)
102
+ }
103
+
104
+ // Function to clear the selection box
105
+ const clearSelectionBox = () => {
106
+ setShowSelectionBox(false)
107
+ setDragStartX(undefined)
108
+ setDragEndX(undefined)
109
+ }
110
+
111
+ // Add keydown event handler to clear selection box when Escape key is pressed
112
+ useEffect(() => {
113
+ const handleKeyDown = (event: KeyboardEvent) => {
114
+ if (event.key === 'Escape' && showSelectionBox) {
115
+ clearSelectionBox()
116
+ }
117
+ }
118
+
119
+ // Add click handler to clear selection box when clicking outside of it
120
+ const handleClickOutside = (event: MouseEvent) => {
121
+ if (
122
+ ref.current &&
123
+ !ref.current.contains(event.target as Node) &&
124
+ showSelectionBox
125
+ ) {
126
+ clearSelectionBox()
127
+ }
128
+ }
129
+
130
+ document.addEventListener('keydown', handleKeyDown)
131
+ document.addEventListener('click', handleClickOutside)
132
+
133
+ return () => {
134
+ document.removeEventListener('keydown', handleKeyDown)
135
+ document.removeEventListener('click', handleClickOutside)
136
+ }
137
+ }, [showSelectionBox, clearSelectionBox])
138
+
38
139
  return (
39
140
  <div
40
141
  ref={ref}
41
- onMouseMove={event => {
42
- const rect = ref.current?.getBoundingClientRect()
43
- const top = rect?.top || 0
44
- const left = rect?.left || 0
45
- setMouseY(event.clientY - top)
46
- setMouseX(event.clientX - left)
142
+ onMouseDown={handleMouseDown}
143
+ onMouseMove={handleMouseMove}
144
+ onMouseUp={handleMouseUp}
145
+ onDoubleClick={() => {
146
+ // Clear selection box on double click
147
+ if (showSelectionBox) {
148
+ clearSelectionBox()
149
+ }
47
150
  }}
48
151
  onMouseLeave={() => {
49
152
  setMouseY(undefined)
50
153
  setMouseX(undefined)
154
+ setIsDragging(false)
51
155
  }}
52
156
  >
53
157
  <BaseLinearDisplayComponent {...props} />
54
158
  <YScaleBars model={model} />
55
- {mouseY && sources ? (
159
+ {mouseY && mouseX && sources && !contextCoord && !showSequenceDialog ? (
56
160
  <div style={{ position: 'relative' }}>
57
- <svg
58
- className={classes.cursor}
161
+ <Crosshairs
59
162
  width={width}
60
163
  height={height}
61
- style={{
62
- position: 'absolute',
63
- top: scrollTop,
64
- }}
65
- >
66
- <line
67
- x1={0}
68
- x2={width}
69
- y1={mouseY - scrollTop}
70
- y2={mouseY - scrollTop}
71
- stroke="black"
72
- />
73
- <line x1={mouseX} x2={mouseX} y1={0} y2={height} stroke="black" />
74
- </svg>
75
- <BaseTooltip>
76
- <SanitizedHTML
77
- html={Object.entries(
78
- sources[Math.floor(mouseY / rowHeight)] || {},
79
- )
80
- .filter(([key]) => key !== 'color' && key !== 'id')
81
- .map(([key, value]) => `${key}:${value}`)
82
- .join('\n')}
83
- />
84
- </BaseTooltip>
164
+ scrollTop={scrollTop}
165
+ mouseX={mouseX}
166
+ mouseY={mouseY}
167
+ />
168
+ <MAFTooltip
169
+ model={model}
170
+ mouseX={mouseX}
171
+ mouseY={mouseY}
172
+ origMouseX={dragStartX}
173
+ rowHeight={rowHeight}
174
+ sources={sources}
175
+ />
85
176
  </div>
86
177
  ) : null}
178
+ {(isDragging || showSelectionBox) &&
179
+ dragStartX !== undefined &&
180
+ dragEndX !== undefined ? (
181
+ <div
182
+ style={{
183
+ position: 'absolute',
184
+ left: Math.min(dragStartX, dragEndX),
185
+ top: 0,
186
+ width: Math.abs(dragEndX - dragStartX),
187
+ height,
188
+ backgroundColor: 'rgba(0, 0, 255, 0.2)',
189
+ border: '1px solid rgba(0, 0, 255, 0.5)',
190
+ pointerEvents: 'none',
191
+ }}
192
+ />
193
+ ) : null}
194
+ <Menu
195
+ open={Boolean(contextCoord)}
196
+ onMenuItemClick={(_, callback) => {
197
+ callback()
198
+ setContextCoord(undefined)
199
+ }}
200
+ onClose={() => {
201
+ setContextCoord(undefined)
202
+ }}
203
+ slotProps={{
204
+ transition: {
205
+ onExit: () => {
206
+ setContextCoord(undefined)
207
+ },
208
+ },
209
+ }}
210
+ anchorReference="anchorPosition"
211
+ anchorPosition={
212
+ contextCoord
213
+ ? { top: contextCoord.coord[1], left: contextCoord.coord[0] }
214
+ : undefined
215
+ }
216
+ style={{
217
+ zIndex: theme.zIndex.tooltip,
218
+ }}
219
+ menuItems={[
220
+ {
221
+ label: 'View subsequence',
222
+ onClick: () => {
223
+ if (!contextCoord) {
224
+ return
225
+ }
226
+
227
+ // Store the selection coordinates for the SequenceDialog to use
228
+ setSelectionCoords({
229
+ dragStartX: contextCoord.dragStartX,
230
+ dragEndX: contextCoord.dragEndX,
231
+ })
232
+
233
+ // Show the dialog
234
+ setShowSequenceDialog(true)
235
+
236
+ // Close the context menu
237
+ setContextCoord(undefined)
238
+ },
239
+ },
240
+ ]}
241
+ />
242
+
243
+ {showSequenceDialog ? (
244
+ <SequenceDialog
245
+ model={model}
246
+ selectionCoords={selectionCoords}
247
+ onClose={() => {
248
+ setShowSequenceDialog(false)
249
+ setSelectionCoords(undefined)
250
+ }}
251
+ />
252
+ ) : null}
87
253
  </div>
88
254
  )
89
255
  })
@@ -0,0 +1,59 @@
1
+ import React from 'react'
2
+
3
+ import { SanitizedHTML } from '@jbrowse/core/ui'
4
+ import BaseTooltip from '@jbrowse/core/ui/BaseTooltip'
5
+ import {
6
+ getBpDisplayStr,
7
+ getContainingView,
8
+ toLocale,
9
+ } from '@jbrowse/core/util'
10
+ import { observer } from 'mobx-react'
11
+
12
+ import type { LinearMafDisplayModel } from '../stateModel'
13
+ import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
14
+
15
+ interface MAFTooltipProps {
16
+ mouseY: number
17
+ mouseX: number
18
+ rowHeight: number
19
+ sources: Record<string, any>[]
20
+ model: LinearMafDisplayModel
21
+ origMouseX?: number
22
+ }
23
+
24
+ const MAFTooltip = observer(function ({
25
+ model,
26
+ mouseY,
27
+ mouseX,
28
+ origMouseX,
29
+ rowHeight,
30
+ sources,
31
+ }: MAFTooltipProps) {
32
+ const view = getContainingView(model) as LinearGenomeViewModel
33
+ const ret = Object.entries(sources[Math.floor(mouseY / rowHeight)] || {})
34
+ .filter(([key]) => key !== 'color' && key !== 'id')
35
+ .map(([key, value]) => `${key}:${value}`)
36
+ .join('\n')
37
+ const p1 = origMouseX ? view.pxToBp(origMouseX) : undefined
38
+ const p2 = view.pxToBp(mouseX)
39
+ return ret ? (
40
+ <BaseTooltip>
41
+ <SanitizedHTML
42
+ html={[
43
+ ret,
44
+ ...(p1
45
+ ? [
46
+ `Start: ${p1.refName}:${toLocale(p1.coord)}`,
47
+ `End: ${p2.refName}:${toLocale(p2.coord)}`,
48
+ `Length: ${getBpDisplayStr(Math.abs(p1.coord - p2.coord))}`,
49
+ ]
50
+ : [`${p2.refName}:${toLocale(p2.coord)}`]),
51
+ ]
52
+ .filter(f => !!f)
53
+ .join('<br/>')}
54
+ />
55
+ </BaseTooltip>
56
+ ) : null
57
+ })
58
+
59
+ export default MAFTooltip
@@ -5,7 +5,7 @@ import { observer } from 'mobx-react'
5
5
  import RectBg from './RectBg'
6
6
  import Tree from './Tree'
7
7
 
8
- import type { LinearMafDisplayModel } from '../stateModel'
8
+ import type { LinearMafDisplayModel } from '../../stateModel'
9
9
 
10
10
  const ColorLegend = observer(function ({
11
11
  model,
@@ -3,7 +3,8 @@ import React from 'react'
3
3
  import { getContainingView } from '@jbrowse/core/util'
4
4
  import { observer } from 'mobx-react'
5
5
 
6
- import type { LinearMafDisplayModel } from '../stateModel'
6
+ import type { LinearMafDisplayModel } from '../../stateModel'
7
+ import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
7
8
 
8
9
  const SvgWrapper = observer(function ({
9
10
  children,
@@ -18,15 +19,17 @@ const SvgWrapper = observer(function ({
18
19
  return <>{children}</>
19
20
  } else {
20
21
  const { totalHeight } = model
22
+ const { width } = getContainingView(model) as LinearGenomeViewModel
21
23
  return (
22
24
  <svg
23
25
  style={{
24
26
  position: 'absolute',
27
+ userSelect: 'none',
25
28
  top: 0,
26
29
  left: 0,
27
30
  pointerEvents: 'none',
28
31
  height: totalHeight,
29
- width: getContainingView(model).width,
32
+ width,
30
33
  }}
31
34
  >
32
35
  {children}
@@ -2,7 +2,7 @@ import React from 'react'
2
2
 
3
3
  import { observer } from 'mobx-react'
4
4
 
5
- import type { LinearMafDisplayModel } from '../stateModel'
5
+ import type { LinearMafDisplayModel } from '../../stateModel'
6
6
 
7
7
  const Tree = observer(function ({ model }: { model: LinearMafDisplayModel }) {
8
8
  const {
@@ -5,7 +5,7 @@ import { observer } from 'mobx-react'
5
5
  import ColorLegend from './ColorLegend'
6
6
  import SvgWrapper from './SvgWrapper'
7
7
 
8
- import type { LinearMafDisplayModel } from '../stateModel'
8
+ import type { LinearMafDisplayModel } from '../../stateModel'
9
9
 
10
10
  export const YScaleBars = observer(function (props: {
11
11
  model: LinearMafDisplayModel
@@ -2,7 +2,7 @@ import React from 'react'
2
2
 
3
3
  import { getContainingView } from '@jbrowse/core/util'
4
4
 
5
- import YScaleBars from './components/YScaleBars'
5
+ import YScaleBars from './components/Sidebar/YScaleBars'
6
6
 
7
7
  import type { LinearMafDisplayModel } from './stateModel'
8
8
  import type {
@@ -22,7 +22,9 @@ import type { ExportSvgDisplayOptions } from '@jbrowse/plugin-linear-genome-view
22
22
  import type { HierarchyNode } from 'd3-hierarchy'
23
23
  import type { Instance } from 'mobx-state-tree'
24
24
 
25
- const SetRowHeightDialog = lazy(() => import('./components/SetRowHeightDialog'))
25
+ const SetRowHeightDialog = lazy(
26
+ () => import('./components/SetRowHeightDialog/SetRowHeightDialog'),
27
+ )
26
28
 
27
29
  /**
28
30
  * #stateModel LinearMafDisplay
@@ -76,6 +78,10 @@ export default function stateModelFactory(
76
78
  * #property
77
79
  */
78
80
  treeAreaWidth: 80,
81
+ /**
82
+ * #property
83
+ */
84
+ showAsUpperCase: true,
79
85
  }),
80
86
  )
81
87
  .volatile(() => ({
@@ -128,6 +134,12 @@ export default function stateModelFactory(
128
134
  self.volatileTree = tree
129
135
  }
130
136
  },
137
+ /**
138
+ * #action
139
+ */
140
+ setShowAsUpperCase(arg: boolean) {
141
+ self.showAsUpperCase = arg
142
+ },
131
143
  }))
132
144
  .views(self => ({
133
145
  /**
@@ -248,6 +260,7 @@ export default function stateModelFactory(
248
260
  rowHeight,
249
261
  rowProportion,
250
262
  mismatchRendering,
263
+ showAsUpperCase,
251
264
  } = self
252
265
  const s = superRenderProps()
253
266
  return {
@@ -260,6 +273,7 @@ export default function stateModelFactory(
260
273
  rowProportion,
261
274
  showAllLetters,
262
275
  mismatchRendering,
276
+ showAsUpperCase,
263
277
  }
264
278
  },
265
279
  /**
@@ -300,6 +314,14 @@ export default function stateModelFactory(
300
314
  },
301
315
  ],
302
316
  },
317
+ {
318
+ label: 'Use upper-case',
319
+ type: 'checkbox',
320
+ checked: self.showAsUpperCase,
321
+ onClick: () => {
322
+ self.setShowAsUpperCase(!self.showAsUpperCase)
323
+ },
324
+ },
303
325
  {
304
326
  label: 'Show all letters',
305
327
  type: 'checkbox',
@@ -16,6 +16,6 @@ export interface NodeWithIdsAndLength {
16
16
 
17
17
  export interface Sample {
18
18
  id: string
19
- label: string
19
+ label?: string
20
20
  color?: string
21
21
  }
@@ -19,6 +19,7 @@ interface RenderArgs extends RenderArgsDeserialized {
19
19
  showAllLetters: boolean
20
20
  mismatchRendering: boolean
21
21
  statusCallback?: (arg: string) => void
22
+ showAsUpperCase: boolean
22
23
  }
23
24
 
24
25
  export default class LinearMafRenderer extends FeatureRendererType {