@jbrowse/plugin-linear-genome-view 1.7.11 → 2.1.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 (272) hide show
  1. package/dist/BaseLinearDisplay/components/BaseLinearDisplay.js +119 -139
  2. package/dist/BaseLinearDisplay/components/BaseLinearDisplay.js.map +1 -0
  3. package/dist/BaseLinearDisplay/components/Block.js +53 -74
  4. package/dist/BaseLinearDisplay/components/Block.js.map +1 -0
  5. package/dist/BaseLinearDisplay/components/LinearBlocks.d.ts +11 -1
  6. package/dist/BaseLinearDisplay/components/LinearBlocks.js +64 -103
  7. package/dist/BaseLinearDisplay/components/LinearBlocks.js.map +1 -0
  8. package/dist/BaseLinearDisplay/components/ServerSideRenderedBlockContent.js +146 -175
  9. package/dist/BaseLinearDisplay/components/ServerSideRenderedBlockContent.js.map +1 -0
  10. package/dist/BaseLinearDisplay/components/Tooltip.js +109 -116
  11. package/dist/BaseLinearDisplay/components/Tooltip.js.map +1 -0
  12. package/dist/BaseLinearDisplay/index.js +13 -40
  13. package/dist/BaseLinearDisplay/index.js.map +1 -0
  14. package/dist/BaseLinearDisplay/models/BaseLinearDisplayModel.d.ts +19 -14
  15. package/dist/BaseLinearDisplay/models/BaseLinearDisplayModel.js +605 -684
  16. package/dist/BaseLinearDisplay/models/BaseLinearDisplayModel.js.map +1 -0
  17. package/dist/BaseLinearDisplay/models/baseLinearDisplayConfigSchema.js +15 -22
  18. package/dist/BaseLinearDisplay/models/baseLinearDisplayConfigSchema.js.map +1 -0
  19. package/dist/BaseLinearDisplay/models/serverSideRenderedBlock.d.ts +7 -8
  20. package/dist/BaseLinearDisplay/models/serverSideRenderedBlock.js +266 -312
  21. package/dist/BaseLinearDisplay/models/serverSideRenderedBlock.js.map +1 -0
  22. package/dist/LinearBareDisplay/configSchema.js +11 -17
  23. package/dist/LinearBareDisplay/configSchema.js.map +1 -0
  24. package/dist/LinearBareDisplay/index.js +7 -20
  25. package/dist/LinearBareDisplay/index.js.map +1 -0
  26. package/dist/LinearBareDisplay/model.d.ts +15 -13
  27. package/dist/LinearBareDisplay/model.js +36 -42
  28. package/dist/LinearBareDisplay/model.js.map +1 -0
  29. package/dist/LinearBasicDisplay/components/SetMaxHeight.d.ts +1 -1
  30. package/dist/LinearBasicDisplay/components/SetMaxHeight.js +76 -85
  31. package/dist/LinearBasicDisplay/components/SetMaxHeight.js.map +1 -0
  32. package/dist/LinearBasicDisplay/configSchema.js +15 -23
  33. package/dist/LinearBasicDisplay/configSchema.js.map +1 -0
  34. package/dist/LinearBasicDisplay/index.js +10 -22
  35. package/dist/LinearBasicDisplay/index.js.map +1 -0
  36. package/dist/LinearBasicDisplay/model.d.ts +20 -14
  37. package/dist/LinearBasicDisplay/model.js +180 -159
  38. package/dist/LinearBasicDisplay/model.js.map +1 -0
  39. package/dist/LinearGenomeView/components/CenterLine.d.ts +2 -8
  40. package/dist/LinearGenomeView/components/CenterLine.js +60 -74
  41. package/dist/LinearGenomeView/components/CenterLine.js.map +1 -0
  42. package/dist/LinearGenomeView/components/ExportSvgDialog.js +141 -141
  43. package/dist/LinearGenomeView/components/ExportSvgDialog.js.map +1 -0
  44. package/dist/LinearGenomeView/components/Gridlines.d.ts +8 -0
  45. package/dist/LinearGenomeView/components/Gridlines.js +78 -0
  46. package/dist/LinearGenomeView/components/Gridlines.js.map +1 -0
  47. package/dist/LinearGenomeView/components/Header.d.ts +1 -0
  48. package/dist/LinearGenomeView/components/Header.js +70 -125
  49. package/dist/LinearGenomeView/components/Header.js.map +1 -0
  50. package/dist/LinearGenomeView/components/HelpDialog.d.ts +0 -1
  51. package/dist/LinearGenomeView/components/HelpDialog.js +62 -44
  52. package/dist/LinearGenomeView/components/HelpDialog.js.map +1 -0
  53. package/dist/LinearGenomeView/components/ImportForm.d.ts +1 -0
  54. package/dist/LinearGenomeView/components/ImportForm.js +222 -289
  55. package/dist/LinearGenomeView/components/ImportForm.js.map +1 -0
  56. package/dist/LinearGenomeView/components/LinearGenomeView.js +60 -124
  57. package/dist/LinearGenomeView/components/LinearGenomeView.js.map +1 -0
  58. package/dist/LinearGenomeView/components/LinearGenomeViewSvg.js +198 -337
  59. package/dist/LinearGenomeView/components/LinearGenomeViewSvg.js.map +1 -0
  60. package/dist/LinearGenomeView/components/MiniControls.js +64 -78
  61. package/dist/LinearGenomeView/components/MiniControls.js.map +1 -0
  62. package/dist/LinearGenomeView/components/OverviewRubberBand.js +229 -293
  63. package/dist/LinearGenomeView/components/OverviewRubberBand.js.map +1 -0
  64. package/dist/LinearGenomeView/components/OverviewScaleBar.d.ts +3 -3
  65. package/dist/LinearGenomeView/components/OverviewScaleBar.js +275 -370
  66. package/dist/LinearGenomeView/components/OverviewScaleBar.js.map +1 -0
  67. package/dist/LinearGenomeView/components/RefNameAutocomplete.d.ts +1 -1
  68. package/dist/LinearGenomeView/components/RefNameAutocomplete.js +237 -324
  69. package/dist/LinearGenomeView/components/RefNameAutocomplete.js.map +1 -0
  70. package/dist/LinearGenomeView/components/RubberBand.js +228 -296
  71. package/dist/LinearGenomeView/components/RubberBand.js.map +1 -0
  72. package/dist/LinearGenomeView/components/Ruler.js +45 -90
  73. package/dist/LinearGenomeView/components/Ruler.js.map +1 -0
  74. package/dist/LinearGenomeView/components/ScaleBar.d.ts +8 -403
  75. package/dist/LinearGenomeView/components/ScaleBar.js +121 -172
  76. package/dist/LinearGenomeView/components/ScaleBar.js.map +1 -0
  77. package/dist/LinearGenomeView/components/SearchBox.js +155 -166
  78. package/dist/LinearGenomeView/components/SearchBox.js.map +1 -0
  79. package/dist/LinearGenomeView/components/SearchResultsDialog.d.ts +0 -1
  80. package/dist/LinearGenomeView/components/SearchResultsDialog.js +105 -149
  81. package/dist/LinearGenomeView/components/SearchResultsDialog.js.map +1 -0
  82. package/dist/LinearGenomeView/components/SequenceDialog.js +219 -272
  83. package/dist/LinearGenomeView/components/SequenceDialog.js.map +1 -0
  84. package/dist/LinearGenomeView/components/TrackContainer.d.ts +2 -1
  85. package/dist/LinearGenomeView/components/TrackContainer.js +113 -158
  86. package/dist/LinearGenomeView/components/TrackContainer.js.map +1 -0
  87. package/dist/LinearGenomeView/components/TrackLabel.d.ts +6 -42
  88. package/dist/LinearGenomeView/components/TrackLabel.js +113 -133
  89. package/dist/LinearGenomeView/components/TrackLabel.js.map +1 -0
  90. package/dist/LinearGenomeView/components/TracksContainer.d.ts +1 -1
  91. package/dist/LinearGenomeView/components/TracksContainer.js +172 -199
  92. package/dist/LinearGenomeView/components/TracksContainer.js.map +1 -0
  93. package/dist/LinearGenomeView/components/ZoomControls.js +72 -87
  94. package/dist/LinearGenomeView/components/ZoomControls.js.map +1 -0
  95. package/dist/LinearGenomeView/components/util.js +94 -71
  96. package/dist/LinearGenomeView/components/util.js.map +1 -0
  97. package/dist/LinearGenomeView/index.d.ts +64 -77
  98. package/dist/LinearGenomeView/index.js +1035 -1412
  99. package/dist/LinearGenomeView/index.js.map +1 -0
  100. package/dist/LinearGenomeView/util.js +76 -83
  101. package/dist/LinearGenomeView/util.js.map +1 -0
  102. package/dist/index.d.ts +98 -48
  103. package/dist/index.js +225 -295
  104. package/dist/index.js.map +1 -0
  105. package/esm/BaseLinearDisplay/components/BaseLinearDisplay.d.ts +9 -0
  106. package/esm/BaseLinearDisplay/components/BaseLinearDisplay.js +68 -0
  107. package/esm/BaseLinearDisplay/components/BaseLinearDisplay.js.map +1 -0
  108. package/esm/BaseLinearDisplay/components/Block.d.ts +15 -0
  109. package/esm/BaseLinearDisplay/components/Block.js +46 -0
  110. package/esm/BaseLinearDisplay/components/Block.js.map +1 -0
  111. package/esm/BaseLinearDisplay/components/LinearBlocks.d.ts +22 -0
  112. package/esm/BaseLinearDisplay/components/LinearBlocks.js +62 -0
  113. package/esm/BaseLinearDisplay/components/LinearBlocks.js.map +1 -0
  114. package/esm/BaseLinearDisplay/components/ServerSideRenderedBlockContent.d.ts +4 -0
  115. package/esm/BaseLinearDisplay/components/ServerSideRenderedBlockContent.js +114 -0
  116. package/esm/BaseLinearDisplay/components/ServerSideRenderedBlockContent.js.map +1 -0
  117. package/esm/BaseLinearDisplay/components/Tooltip.d.ts +8 -0
  118. package/esm/BaseLinearDisplay/components/Tooltip.js +64 -0
  119. package/esm/BaseLinearDisplay/components/Tooltip.js.map +1 -0
  120. package/esm/BaseLinearDisplay/index.d.ts +5 -0
  121. package/esm/BaseLinearDisplay/index.js +4 -0
  122. package/esm/BaseLinearDisplay/index.js.map +1 -0
  123. package/esm/BaseLinearDisplay/models/BaseLinearDisplayModel.d.ts +232 -0
  124. package/esm/BaseLinearDisplay/models/BaseLinearDisplayModel.js +541 -0
  125. package/esm/BaseLinearDisplay/models/BaseLinearDisplayModel.js.map +1 -0
  126. package/esm/BaseLinearDisplay/models/baseLinearDisplayConfigSchema.d.ts +1 -0
  127. package/esm/BaseLinearDisplay/models/baseLinearDisplayConfigSchema.js +14 -0
  128. package/esm/BaseLinearDisplay/models/baseLinearDisplayConfigSchema.js.map +1 -0
  129. package/esm/BaseLinearDisplay/models/serverSideRenderedBlock.d.ts +96 -0
  130. package/esm/BaseLinearDisplay/models/serverSideRenderedBlock.js +225 -0
  131. package/esm/BaseLinearDisplay/models/serverSideRenderedBlock.js.map +1 -0
  132. package/esm/LinearBareDisplay/configSchema.d.ts +2 -0
  133. package/esm/LinearBareDisplay/configSchema.js +9 -0
  134. package/esm/LinearBareDisplay/configSchema.js.map +1 -0
  135. package/esm/LinearBareDisplay/index.d.ts +2 -0
  136. package/esm/LinearBareDisplay/index.js +3 -0
  137. package/esm/LinearBareDisplay/index.js.map +1 -0
  138. package/esm/LinearBareDisplay/model.d.ts +194 -0
  139. package/esm/LinearBareDisplay/model.js +28 -0
  140. package/esm/LinearBareDisplay/model.js.map +1 -0
  141. package/esm/LinearBasicDisplay/components/SetMaxHeight.d.ts +10 -0
  142. package/esm/LinearBasicDisplay/components/SetMaxHeight.js +40 -0
  143. package/esm/LinearBasicDisplay/components/SetMaxHeight.js.map +1 -0
  144. package/esm/LinearBasicDisplay/configSchema.d.ts +2 -0
  145. package/esm/LinearBasicDisplay/configSchema.js +14 -0
  146. package/esm/LinearBasicDisplay/configSchema.js.map +1 -0
  147. package/esm/LinearBasicDisplay/index.d.ts +2 -0
  148. package/esm/LinearBasicDisplay/index.js +3 -0
  149. package/esm/LinearBasicDisplay/index.js.map +1 -0
  150. package/esm/LinearBasicDisplay/model.d.ts +218 -0
  151. package/esm/LinearBasicDisplay/model.js +127 -0
  152. package/esm/LinearBasicDisplay/model.js.map +1 -0
  153. package/esm/LinearGenomeView/components/CenterLine.d.ts +8 -0
  154. package/esm/LinearGenomeView/components/CenterLine.js +40 -0
  155. package/esm/LinearGenomeView/components/CenterLine.js.map +1 -0
  156. package/esm/LinearGenomeView/components/ExportSvgDialog.d.ts +6 -0
  157. package/esm/LinearGenomeView/components/ExportSvgDialog.js +52 -0
  158. package/esm/LinearGenomeView/components/ExportSvgDialog.js.map +1 -0
  159. package/esm/LinearGenomeView/components/Gridlines.d.ts +8 -0
  160. package/esm/LinearGenomeView/components/Gridlines.js +71 -0
  161. package/esm/LinearGenomeView/components/Gridlines.js.map +1 -0
  162. package/esm/LinearGenomeView/components/Header.d.ts +7 -0
  163. package/esm/LinearGenomeView/components/Header.js +81 -0
  164. package/esm/LinearGenomeView/components/Header.js.map +1 -0
  165. package/esm/LinearGenomeView/components/HelpDialog.d.ts +4 -0
  166. package/esm/LinearGenomeView/components/HelpDialog.js +58 -0
  167. package/esm/LinearGenomeView/components/HelpDialog.js.map +1 -0
  168. package/esm/LinearGenomeView/components/ImportForm.d.ts +7 -0
  169. package/esm/LinearGenomeView/components/ImportForm.js +141 -0
  170. package/esm/LinearGenomeView/components/ImportForm.js.map +1 -0
  171. package/esm/LinearGenomeView/components/LinearGenomeView.d.ts +7 -0
  172. package/esm/LinearGenomeView/components/LinearGenomeView.js +63 -0
  173. package/esm/LinearGenomeView/components/LinearGenomeView.js.map +1 -0
  174. package/esm/LinearGenomeView/components/LinearGenomeViewSvg.d.ts +4 -0
  175. package/esm/LinearGenomeView/components/LinearGenomeViewSvg.js +132 -0
  176. package/esm/LinearGenomeView/components/LinearGenomeViewSvg.js.map +1 -0
  177. package/esm/LinearGenomeView/components/MiniControls.d.ts +6 -0
  178. package/esm/LinearGenomeView/components/MiniControls.js +25 -0
  179. package/esm/LinearGenomeView/components/MiniControls.js.map +1 -0
  180. package/esm/LinearGenomeView/components/OverviewRubberBand.d.ts +22 -0
  181. package/esm/LinearGenomeView/components/OverviewRubberBand.js +197 -0
  182. package/esm/LinearGenomeView/components/OverviewRubberBand.js.map +1 -0
  183. package/esm/LinearGenomeView/components/OverviewScaleBar.d.ts +148 -0
  184. package/esm/LinearGenomeView/components/OverviewScaleBar.js +287 -0
  185. package/esm/LinearGenomeView/components/OverviewScaleBar.js.map +1 -0
  186. package/esm/LinearGenomeView/components/RefNameAutocomplete.d.ts +22 -0
  187. package/esm/LinearGenomeView/components/RefNameAutocomplete.js +136 -0
  188. package/esm/LinearGenomeView/components/RefNameAutocomplete.js.map +1 -0
  189. package/esm/LinearGenomeView/components/RubberBand.d.ts +9 -0
  190. package/esm/LinearGenomeView/components/RubberBand.js +197 -0
  191. package/esm/LinearGenomeView/components/RubberBand.js.map +1 -0
  192. package/esm/LinearGenomeView/components/Ruler.d.ts +27 -0
  193. package/esm/LinearGenomeView/components/Ruler.js +50 -0
  194. package/esm/LinearGenomeView/components/Ruler.js.map +1 -0
  195. package/esm/LinearGenomeView/components/ScaleBar.d.ts +10 -0
  196. package/esm/LinearGenomeView/components/ScaleBar.js +112 -0
  197. package/esm/LinearGenomeView/components/ScaleBar.js.map +1 -0
  198. package/esm/LinearGenomeView/components/SearchBox.d.ts +8 -0
  199. package/esm/LinearGenomeView/components/SearchBox.js +94 -0
  200. package/esm/LinearGenomeView/components/SearchBox.js.map +1 -0
  201. package/esm/LinearGenomeView/components/SearchResultsDialog.d.ts +7 -0
  202. package/esm/LinearGenomeView/components/SearchResultsDialog.js +107 -0
  203. package/esm/LinearGenomeView/components/SearchResultsDialog.js.map +1 -0
  204. package/esm/LinearGenomeView/components/SequenceDialog.d.ts +8 -0
  205. package/esm/LinearGenomeView/components/SequenceDialog.js +147 -0
  206. package/esm/LinearGenomeView/components/SequenceDialog.js.map +1 -0
  207. package/esm/LinearGenomeView/components/TrackContainer.d.ts +10 -0
  208. package/esm/LinearGenomeView/components/TrackContainer.js +104 -0
  209. package/esm/LinearGenomeView/components/TrackContainer.js.map +1 -0
  210. package/esm/LinearGenomeView/components/TrackLabel.d.ts +8 -0
  211. package/esm/LinearGenomeView/components/TrackLabel.js +88 -0
  212. package/esm/LinearGenomeView/components/TrackLabel.js.map +1 -0
  213. package/{dist/LinearGenomeView/components/VerticalGuides.d.ts → esm/LinearGenomeView/components/TracksContainer.d.ts} +4 -3
  214. package/esm/LinearGenomeView/components/TracksContainer.js +142 -0
  215. package/esm/LinearGenomeView/components/TracksContainer.js.map +1 -0
  216. package/esm/LinearGenomeView/components/ZoomControls.d.ts +7 -0
  217. package/esm/LinearGenomeView/components/ZoomControls.js +32 -0
  218. package/esm/LinearGenomeView/components/ZoomControls.js.map +1 -0
  219. package/esm/LinearGenomeView/components/util.d.ts +14 -0
  220. package/esm/LinearGenomeView/components/util.js +17 -0
  221. package/esm/LinearGenomeView/components/util.js.map +1 -0
  222. package/esm/LinearGenomeView/index.d.ts +275 -0
  223. package/esm/LinearGenomeView/index.js +978 -0
  224. package/esm/LinearGenomeView/index.js.map +1 -0
  225. package/esm/LinearGenomeView/util.d.ts +14 -0
  226. package/esm/LinearGenomeView/util.js +62 -0
  227. package/esm/LinearGenomeView/util.js.map +1 -0
  228. package/esm/index.d.ts +615 -0
  229. package/esm/index.js +127 -0
  230. package/esm/index.js.map +1 -0
  231. package/package.json +22 -15
  232. package/src/BaseLinearDisplay/components/BaseLinearDisplay.tsx +4 -4
  233. package/src/BaseLinearDisplay/components/Block.tsx +5 -5
  234. package/src/BaseLinearDisplay/components/LinearBlocks.tsx +4 -4
  235. package/src/BaseLinearDisplay/components/ServerSideRenderedBlockContent.tsx +9 -9
  236. package/src/BaseLinearDisplay/components/Tooltip.tsx +14 -4
  237. package/src/BaseLinearDisplay/models/BaseLinearDisplayModel.tsx +6 -4
  238. package/src/BaseLinearDisplay/models/serverSideRenderedBlock.ts +1 -1
  239. package/src/LinearBasicDisplay/components/SetMaxHeight.tsx +10 -7
  240. package/src/LinearBasicDisplay/model.ts +19 -11
  241. package/src/LinearGenomeView/components/CenterLine.tsx +6 -11
  242. package/src/LinearGenomeView/components/ExportSvgDialog.tsx +5 -5
  243. package/src/LinearGenomeView/components/{VerticalGuides.tsx → Gridlines.tsx} +9 -11
  244. package/src/LinearGenomeView/components/Header.tsx +10 -15
  245. package/src/LinearGenomeView/components/HelpDialog.tsx +5 -5
  246. package/src/LinearGenomeView/components/ImportForm.tsx +6 -12
  247. package/src/LinearGenomeView/components/LinearGenomeView.test.js +16 -6
  248. package/src/LinearGenomeView/components/LinearGenomeView.tsx +6 -9
  249. package/src/LinearGenomeView/components/MiniControls.tsx +29 -40
  250. package/src/LinearGenomeView/components/OverviewRubberBand.tsx +20 -29
  251. package/src/LinearGenomeView/components/OverviewScaleBar.tsx +61 -59
  252. package/src/LinearGenomeView/components/RefNameAutocomplete.tsx +13 -44
  253. package/src/LinearGenomeView/components/RubberBand.tsx +12 -18
  254. package/src/LinearGenomeView/components/Ruler.tsx +5 -11
  255. package/src/LinearGenomeView/components/ScaleBar.tsx +23 -27
  256. package/src/LinearGenomeView/components/SearchBox.tsx +4 -3
  257. package/src/LinearGenomeView/components/SearchResultsDialog.tsx +7 -6
  258. package/src/LinearGenomeView/components/SequenceDialog.tsx +10 -10
  259. package/src/LinearGenomeView/components/TrackContainer.tsx +29 -39
  260. package/src/LinearGenomeView/components/TrackLabel.tsx +76 -79
  261. package/src/LinearGenomeView/components/TracksContainer.tsx +10 -15
  262. package/src/LinearGenomeView/components/ZoomControls.tsx +12 -13
  263. package/src/LinearGenomeView/components/__snapshots__/LinearGenomeView.test.js.snap +500 -550
  264. package/src/LinearGenomeView/index.test.ts +15 -36
  265. package/src/LinearGenomeView/index.tsx +390 -564
  266. package/src/index.ts +1 -1
  267. package/dist/LinearBareDisplay/index.test.js +0 -33
  268. package/dist/LinearGenomeView/components/LinearGenomeView.test.js +0 -234
  269. package/dist/LinearGenomeView/components/ScaleBar.test.js +0 -180
  270. package/dist/LinearGenomeView/components/VerticalGuides.js +0 -116
  271. package/dist/LinearGenomeView/index.test.js +0 -1187
  272. package/dist/LinearGenomeView/util.test.js +0 -78
@@ -14,7 +14,6 @@ import {
14
14
  measureText,
15
15
  parseLocString,
16
16
  springAnimate,
17
- viewBpToPx,
18
17
  } from '@jbrowse/core/util'
19
18
  import BaseResult from '@jbrowse/core/TextSearch/BaseResults'
20
19
  import { BlockSet, BaseBlock } from '@jbrowse/core/util/blockTypes'
@@ -33,19 +32,20 @@ import {
33
32
  } from 'mobx-state-tree'
34
33
 
35
34
  import Base1DView from '@jbrowse/core/util/Base1DViewModel'
36
- import PluginManager from '@jbrowse/core/PluginManager'
37
- import clone from 'clone'
35
+ import { moveTo, pxToBp, bpToPx } from '@jbrowse/core/util/Base1DUtils'
38
36
  import { saveAs } from 'file-saver'
37
+ import clone from 'clone'
38
+ import PluginManager from '@jbrowse/core/PluginManager'
39
39
 
40
40
  // icons
41
41
  import { TrackSelector as TrackSelectorIcon } from '@jbrowse/core/ui/Icons'
42
- import SyncAltIcon from '@material-ui/icons/SyncAlt'
43
- import VisibilityIcon from '@material-ui/icons/Visibility'
44
- import LabelIcon from '@material-ui/icons/Label'
45
- import FolderOpenIcon from '@material-ui/icons/FolderOpen'
46
- import PhotoCameraIcon from '@material-ui/icons/PhotoCamera'
47
- import ZoomInIcon from '@material-ui/icons/ZoomIn'
48
- import MenuOpenIcon from '@material-ui/icons/MenuOpen'
42
+ import SyncAltIcon from '@mui/icons-material/SyncAlt'
43
+ import VisibilityIcon from '@mui/icons-material/Visibility'
44
+ import LabelIcon from '@mui/icons-material/Label'
45
+ import FolderOpenIcon from '@mui/icons-material/FolderOpen'
46
+ import PhotoCameraIcon from '@mui/icons-material/PhotoCamera'
47
+ import ZoomInIcon from '@mui/icons-material/ZoomIn'
48
+ import MenuOpenIcon from '@mui/icons-material/MenuOpen'
49
49
 
50
50
  // locals
51
51
  import { renderToSvg } from './components/LinearGenomeViewSvg'
@@ -144,6 +144,7 @@ export function stateModelFactory(pluginManager: PluginManager) {
144
144
  const setting = localStorageGetItem('lgv-showCytobands')
145
145
  return setting !== undefined && setting !== null ? !!+setting : true
146
146
  }),
147
+ showGridlines: true,
147
148
  }),
148
149
  )
149
150
  .volatile(() => ({
@@ -288,99 +289,6 @@ export function stateModelFactory(pluginManager: PluginManager) {
288
289
  }
289
290
  },
290
291
 
291
- bpToPx({
292
- refName,
293
- coord,
294
- regionNumber,
295
- }: {
296
- refName: string
297
- coord: number
298
- regionNumber?: number
299
- }) {
300
- return viewBpToPx({ refName, coord, regionNumber, self })
301
- },
302
- /**
303
- *
304
- * @param px - px in the view area, return value is the displayed regions
305
- * @returns BpOffset of the displayed region that it lands in
306
- */
307
- pxToBp(px: number) {
308
- let bpSoFar = 0
309
- const bp = (self.offsetPx + px) * self.bpPerPx
310
- const n = self.displayedRegions.length
311
- if (bp < 0) {
312
- const region = self.displayedRegions[0]
313
- const offset = bp
314
- return {
315
- ...getSnapshot(region),
316
- oob: true,
317
- coord: region.reversed
318
- ? Math.floor(region.end - offset) + 1
319
- : Math.floor(region.start + offset) + 1,
320
- offset,
321
- index: 0,
322
- }
323
- }
324
-
325
- const interRegionPaddingBp = self.interRegionPaddingWidth * self.bpPerPx
326
- const minimumBlockBp = self.minimumBlockWidth * self.bpPerPx
327
-
328
- for (let index = 0; index < self.displayedRegions.length; index += 1) {
329
- const region = self.displayedRegions[index]
330
- const len = region.end - region.start
331
- const offset = bp - bpSoFar
332
- if (len + bpSoFar > bp && bpSoFar <= bp) {
333
- return {
334
- ...getSnapshot(region),
335
- oob: false,
336
- offset,
337
- coord: region.reversed
338
- ? Math.floor(region.end - offset) + 1
339
- : Math.floor(region.start + offset) + 1,
340
- index,
341
- }
342
- }
343
-
344
- // add the interRegionPaddingWidth if the boundary is in the screen
345
- // e.g. offset>0 && offset<width
346
- if (
347
- region.end - region.start > minimumBlockBp &&
348
- offset / self.bpPerPx > 0 &&
349
- offset / self.bpPerPx < self.width
350
- ) {
351
- bpSoFar += len + interRegionPaddingBp
352
- } else {
353
- bpSoFar += len
354
- }
355
- }
356
-
357
- if (bp >= bpSoFar) {
358
- const region = self.displayedRegions[n - 1]
359
- const len = region.end - region.start
360
- const offset = bp - bpSoFar + len
361
- return {
362
- ...getSnapshot(region),
363
- oob: true,
364
- offset,
365
- coord: region.reversed
366
- ? Math.floor(region.end - offset) + 1
367
- : Math.floor(region.start + offset) + 1,
368
- index: n - 1,
369
- }
370
- }
371
- return {
372
- coord: 0,
373
- index: 0,
374
- refName: '',
375
- oob: true,
376
- assemblyName: '',
377
- offset: 0,
378
- start: 0,
379
- end: 0,
380
- reversed: false,
381
- }
382
- },
383
-
384
292
  getTrack(id: string) {
385
293
  return self.tracks.find(t => t.configuration.trackId === id)
386
294
  },
@@ -431,12 +339,6 @@ export function stateModelFactory(pluginManager: PluginManager) {
431
339
 
432
340
  return allActions
433
341
  },
434
-
435
- get centerLineInfo() {
436
- return self.displayedRegions.length
437
- ? this.pxToBp(self.width / 2)
438
- : undefined
439
- },
440
342
  }))
441
343
  .actions(self => ({
442
344
  setShowCytobands(flag: boolean) {
@@ -460,6 +362,9 @@ export function stateModelFactory(pluginManager: PluginManager) {
460
362
  toggleNoTracksActive() {
461
363
  self.hideNoTracksActive = !self.hideNoTracksActive
462
364
  },
365
+ toggleShowGridlines() {
366
+ self.showGridlines = !self.showGridlines
367
+ },
463
368
 
464
369
  scrollTo(offsetPx: number) {
465
370
  const newOffsetPx = clamp(offsetPx, self.minOffset, self.maxOffset)
@@ -529,26 +434,19 @@ export function stateModelFactory(pluginManager: PluginManager) {
529
434
  initialSnapshot = {},
530
435
  displayInitialSnapshot = {},
531
436
  ) {
532
- const trackConfigSchema =
533
- pluginManager.pluggableConfigSchemaType('track')
534
- const configuration = resolveIdentifier(
535
- trackConfigSchema,
536
- getRoot(self),
537
- trackId,
538
- )
539
- if (!configuration) {
437
+ const schema = pluginManager.pluggableConfigSchemaType('track')
438
+ const conf = resolveIdentifier(schema, getRoot(self), trackId)
439
+ if (!conf) {
540
440
  throw new Error(`Could not resolve identifier "${trackId}"`)
541
441
  }
542
- const trackType = pluginManager.getTrackType(configuration?.type)
442
+ const trackType = pluginManager.getTrackType(conf?.type)
543
443
  if (!trackType) {
544
- throw new Error(`Unknown track type ${configuration.type}`)
444
+ throw new Error(`Unknown track type ${conf.type}`)
545
445
  }
546
446
  const viewType = pluginManager.getViewType(self.type)
547
- const supportedDisplays = viewType.displayTypes.map(
548
- displayType => displayType.name,
549
- )
550
- const displayConf = configuration.displays.find(
551
- (d: AnyConfigurationModel) => supportedDisplays.includes(d.type),
447
+ const supportedDisplays = viewType.displayTypes.map(d => d.name)
448
+ const displayConf = conf.displays.find((d: AnyConfigurationModel) =>
449
+ supportedDisplays.includes(d.type),
552
450
  )
553
451
  if (!displayConf) {
554
452
  throw new Error(
@@ -556,14 +454,12 @@ export function stateModelFactory(pluginManager: PluginManager) {
556
454
  )
557
455
  }
558
456
 
559
- const shownTracks = self.tracks.filter(
560
- t => t.configuration === configuration,
561
- )
562
- if (shownTracks.length === 0) {
457
+ const t = self.tracks.filter(t => t.configuration === conf)
458
+ if (t.length === 0) {
563
459
  const track = trackType.stateModel.create({
564
460
  ...initialSnapshot,
565
- type: configuration.type,
566
- configuration,
461
+ type: conf.type,
462
+ configuration: conf,
567
463
  displays: [
568
464
  {
569
465
  type: displayConf.type,
@@ -575,38 +471,26 @@ export function stateModelFactory(pluginManager: PluginManager) {
575
471
  self.tracks.push(track)
576
472
  return track
577
473
  }
578
- return shownTracks[0]
474
+ return t[0]
579
475
  },
580
476
 
581
477
  hideTrack(trackId: string) {
582
- const trackConfigSchema =
583
- pluginManager.pluggableConfigSchemaType('track')
584
- const configuration = resolveIdentifier(
585
- trackConfigSchema,
586
- getRoot(self),
587
- trackId,
588
- )
589
- // if we have any tracks with that configuration, turn them off
590
- const shownTracks = self.tracks.filter(
591
- t => t.configuration === configuration,
592
- )
593
- transaction(() => shownTracks.forEach(t => self.tracks.remove(t)))
594
- return shownTracks.length
478
+ const schema = pluginManager.pluggableConfigSchemaType('track')
479
+ const conf = resolveIdentifier(schema, getRoot(self), trackId)
480
+ const t = self.tracks.filter(t => t.configuration === conf)
481
+ transaction(() => t.forEach(t => self.tracks.remove(t)))
482
+ return t.length
595
483
  },
596
484
  }))
597
485
  .actions(self => ({
598
- moveTrack(movingTrackId: string, targetTrackId: string) {
599
- const oldIndex = self.tracks.findIndex(
600
- track => track.id === movingTrackId,
601
- )
486
+ moveTrack(movingId: string, targetId: string) {
487
+ const oldIndex = self.tracks.findIndex(track => track.id === movingId)
602
488
  if (oldIndex === -1) {
603
- throw new Error(`Track ID ${movingTrackId} not found`)
489
+ throw new Error(`Track ID ${movingId} not found`)
604
490
  }
605
- const newIndex = self.tracks.findIndex(
606
- track => track.id === targetTrackId,
607
- )
491
+ const newIndex = self.tracks.findIndex(track => track.id === targetId)
608
492
  if (newIndex === -1) {
609
- throw new Error(`Track ID ${targetTrackId} not found`)
493
+ throw new Error(`Track ID ${targetId} not found`)
610
494
  }
611
495
  const track = getSnapshot(self.tracks[oldIndex])
612
496
  self.tracks.splice(oldIndex, 1)
@@ -666,429 +550,83 @@ export function stateModelFactory(pluginManager: PluginManager) {
666
550
  throw new Error(`invalid track selector type ${self.trackSelectorType}`)
667
551
  },
668
552
 
669
- navToLocString(locString: string, optAssemblyName?: string) {
670
- const { assemblyNames } = self
671
- const { assemblyManager } = getSession(self)
672
- const { isValidRefName } = assemblyManager
673
- const assemblyName = optAssemblyName || assemblyNames[0]
674
- let parsedLocStrings
675
- const inputs = locString
676
- .split(/(\s+)/)
677
- .map(f => f.trim())
678
- .filter(f => !!f)
679
-
680
- // first try interpreting as a whitespace-separated sequence of
681
- // multiple locstrings
682
- try {
683
- parsedLocStrings = inputs.map(l =>
684
- parseLocString(l, ref => isValidRefName(ref, assemblyName)),
685
- )
686
- } catch (e) {
687
- // if this fails, try interpreting as a whitespace-separated refname,
688
- // start, end if start and end are integer inputs
689
- const [refName, start, end] = inputs
690
- if (
691
- `${e}`.match(/Unknown reference sequence/) &&
692
- Number.isInteger(+start) &&
693
- Number.isInteger(+end)
694
- ) {
695
- parsedLocStrings = [
696
- parseLocString(refName + ':' + start + '..' + end, ref =>
697
- isValidRefName(ref, assemblyName),
698
- ),
699
- ]
700
- } else {
701
- throw e
702
- }
703
- }
553
+ /**
554
+ * Helper method for the fetchSequence.
555
+ * Retrieves the corresponding regions that were selected by the rubberband
556
+ *
557
+ * @param leftOffset - `object as {start, end, index, offset}`, offset = start of user drag
558
+ * @param rightOffset - `object as {start, end, index, offset}`, offset = end of user drag
559
+ * @returns array of Region[]
560
+ */
561
+ getSelectedRegions(leftOffset?: BpOffset, rightOffset?: BpOffset) {
562
+ const snap = getSnapshot(self)
563
+ const simView = Base1DView.create({
564
+ // xref https://github.com/mobxjs/mobx-state-tree/issues/1524 for Omit
565
+ ...(snap as Omit<typeof self, symbol>),
566
+ interRegionPaddingWidth: self.interRegionPaddingWidth,
567
+ })
704
568
 
705
- const locations = parsedLocStrings?.map(region => {
706
- const asmName = region.assemblyName || assemblyName
707
- const asm = assemblyManager.get(asmName)
708
- const { refName } = region
709
- if (!asm) {
710
- throw new Error(`assembly ${asmName} not found`)
711
- }
712
- const { regions } = asm
713
- if (!regions) {
714
- throw new Error(`regions not loaded yet for ${asmName}`)
715
- }
716
- const canonicalRefName = asm.getCanonicalRefName(region.refName)
717
- if (!canonicalRefName) {
718
- throw new Error(`Could not find refName ${refName} in ${asm.name}`)
719
- }
720
- const parentRegion = regions.find(
721
- region => region.refName === canonicalRefName,
722
- )
723
- if (!parentRegion) {
724
- throw new Error(`Could not find refName ${refName} in ${asmName}`)
725
- }
569
+ simView.setVolatileWidth(self.width)
570
+ simView.moveTo(leftOffset, rightOffset)
726
571
 
727
- return {
728
- ...region,
729
- assemblyName: asmName,
730
- parentRegion,
731
- }
732
- })
572
+ return simView.dynamicBlocks.contentBlocks.map(region => ({
573
+ ...region,
574
+ start: Math.floor(region.start),
575
+ end: Math.ceil(region.end),
576
+ }))
577
+ },
733
578
 
734
- if (locations.length === 1) {
735
- const loc = locations[0]
736
- this.setDisplayedRegions([
737
- { reversed: loc.reversed, ...loc.parentRegion },
738
- ])
739
- const { start, end, parentRegion } = loc
579
+ // schedule something to be run after the next time displayedRegions is set
580
+ afterDisplayedRegionsSet(cb: Function) {
581
+ self.afterDisplayedRegionsSetCallbacks.push(cb)
582
+ },
740
583
 
741
- this.navTo({
742
- ...loc,
743
- start: clamp(start ?? 0, 0, parentRegion.end),
744
- end: clamp(end ?? parentRegion.end, 0, parentRegion.end),
745
- })
746
- } else {
747
- this.setDisplayedRegions(
748
- // @ts-ignore
749
- locations.map(r => (r.start === undefined ? r.parentRegion : r)),
750
- )
751
- this.showAllRegions()
752
- }
584
+ horizontalScroll(distance: number) {
585
+ const oldOffsetPx = self.offsetPx
586
+ // newOffsetPx is the actual offset after the scroll is clamped
587
+ const newOffsetPx = self.scrollTo(self.offsetPx + distance)
588
+ return newOffsetPx - oldOffsetPx
753
589
  },
754
590
 
755
- /**
756
- * Navigate to a location based on its refName and optionally start, end,
757
- * and assemblyName. Can handle if there are multiple displayedRegions
758
- * from same refName. Only navigates to a location if it is entirely
759
- * within a displayedRegion. Navigates to the first matching location
760
- * encountered.
761
- *
762
- * Throws an error if navigation was unsuccessful
763
- *
764
- * @param location - a proposed location to navigate to
765
- */
766
- navTo(query: NavLocation) {
767
- this.navToMultiple([query])
591
+ center() {
592
+ const centerBp = self.totalBp / 2
593
+ const centerPx = centerBp / self.bpPerPx
594
+ self.scrollTo(Math.round(centerPx - self.width / 2))
768
595
  },
769
596
 
770
- navToMultiple(locations: NavLocation[]) {
771
- const firstLocation = locations[0]
772
- let { refName } = firstLocation
773
- const {
774
- start,
775
- end,
776
- assemblyName = self.assemblyNames[0],
777
- } = firstLocation
597
+ showAllRegions() {
598
+ self.zoomTo(self.maxBpPerPx)
599
+ this.center()
600
+ },
778
601
 
779
- if (start !== undefined && end !== undefined && start > end) {
780
- throw new Error(`start "${start + 1}" is greater than end "${end}"`)
781
- }
602
+ showAllRegionsInAssembly(assemblyName?: string) {
782
603
  const session = getSession(self)
783
604
  const { assemblyManager } = session
784
- const assembly = assemblyManager.get(assemblyName)
785
- if (assembly) {
786
- const canonicalRefName = assembly.getCanonicalRefName(refName)
787
- if (canonicalRefName) {
788
- refName = canonicalRefName
605
+ if (!assemblyName) {
606
+ const assemblyNames = [
607
+ ...new Set(
608
+ self.displayedRegions.map(region => region.assemblyName),
609
+ ),
610
+ ]
611
+ if (assemblyNames.length > 1) {
612
+ session.notify(
613
+ `Can't perform this with multiple assemblies currently`,
614
+ )
615
+ return
789
616
  }
617
+
618
+ ;[assemblyName] = assemblyNames
790
619
  }
791
- let s = start
792
- let e = end
793
- let refNameMatched = false
794
- const predicate = (r: Region) => {
795
- if (refName === r.refName) {
796
- refNameMatched = true
797
- if (s === undefined) {
798
- s = r.start
799
- }
800
- if (e === undefined) {
801
- e = r.end
802
- }
803
- if (s >= r.start && s <= r.end && e <= r.end && e >= r.start) {
804
- return true
805
- }
806
- s = start
807
- e = end
620
+ const assembly = assemblyManager.get(assemblyName)
621
+ if (assembly) {
622
+ const { regions } = assembly
623
+ if (regions) {
624
+ this.setDisplayedRegions(regions)
625
+ self.zoomTo(self.maxBpPerPx)
626
+ this.center()
808
627
  }
809
- return false
810
628
  }
811
-
812
- const lastIndex = findLastIndex(self.displayedRegions, predicate)
813
- let index
814
- while (index !== lastIndex) {
815
- try {
816
- const previousIndex: number | undefined = index
817
- index = self.displayedRegions
818
- .slice(previousIndex === undefined ? 0 : previousIndex + 1)
819
- .findIndex(predicate)
820
- if (previousIndex !== undefined) {
821
- index += previousIndex + 1
822
- }
823
- if (!refNameMatched) {
824
- throw new Error(
825
- `could not find a region with refName "${refName}"`,
826
- )
827
- }
828
- if (s === undefined) {
829
- throw new Error(
830
- `could not find a region with refName "${refName}" that contained an end position ${e}`,
831
- )
832
- }
833
- if (e === undefined) {
834
- throw new Error(
835
- `could not find a region with refName "${refName}" that contained a start position ${
836
- s + 1
837
- }`,
838
- )
839
- }
840
- if (index === -1) {
841
- throw new Error(
842
- `could not find a region that completely contained "${assembleLocString(
843
- firstLocation,
844
- )}"`,
845
- )
846
- }
847
- if (locations.length === 1) {
848
- const f = self.displayedRegions[index]
849
- this.moveTo(
850
- { index, offset: f.reversed ? f.end - e : s - f.start },
851
- { index, offset: f.reversed ? f.end - s : e - f.start },
852
- )
853
- return
854
- }
855
- let locationIndex = 0
856
- let locationStart = 0
857
- let locationEnd = 0
858
- for (
859
- locationIndex;
860
- locationIndex < locations.length;
861
- locationIndex++
862
- ) {
863
- const location = locations[locationIndex]
864
- const region = self.displayedRegions[index + locationIndex]
865
- locationStart = location.start || region.start
866
- locationEnd = location.end || region.end
867
- if (location.refName !== region.refName) {
868
- throw new Error(
869
- `Entered location ${assembleLocString(
870
- location,
871
- )} does not match with displayed regions`,
872
- )
873
- }
874
- }
875
- locationIndex -= 1
876
- const startDisplayedRegion = self.displayedRegions[index]
877
- const endDisplayedRegion =
878
- self.displayedRegions[index + locationIndex]
879
- this.moveTo(
880
- {
881
- index,
882
- offset: startDisplayedRegion.reversed
883
- ? startDisplayedRegion.end - e
884
- : s - startDisplayedRegion.start,
885
- },
886
- {
887
- index: index + locationIndex,
888
- offset: endDisplayedRegion.reversed
889
- ? endDisplayedRegion.end - locationStart
890
- : locationEnd - endDisplayedRegion.start,
891
- },
892
- )
893
- return
894
- } catch (error) {
895
- if (index === lastIndex) {
896
- throw error
897
- }
898
- }
899
- }
900
- },
901
-
902
- /**
903
- * Navigate to a location based on user clicking and dragging on the
904
- * overview scale bar to select a region to zoom into.
905
- * Can handle if there are multiple displayedRegions from same refName.
906
- * Only navigates to a location if it is entirely within a displayedRegion.
907
- *
908
- * @param leftPx- `object as {start, end, index, offset}`, offset = start of user drag
909
- * @param rightPx- `object as {start, end, index, offset}`, offset = end of user drag
910
- */
911
- zoomToDisplayedRegions(leftPx: BpOffset, rightPx: BpOffset) {
912
- if (leftPx === undefined || rightPx === undefined) {
913
- return
914
- }
915
-
916
- const singleRefSeq =
917
- leftPx.refName === rightPx.refName && leftPx.index === rightPx.index
918
- // zooming into one displayed Region
919
- if (
920
- (singleRefSeq && rightPx.offset < leftPx.offset) ||
921
- leftPx.index > rightPx.index
922
- ) {
923
- ;[leftPx, rightPx] = [rightPx, leftPx]
924
- }
925
- const startOffset = {
926
- start: leftPx.start,
927
- end: leftPx.end,
928
- index: leftPx.index,
929
- offset: leftPx.offset,
930
- }
931
- const endOffset = {
932
- start: rightPx.start,
933
- end: rightPx.end,
934
- index: rightPx.index,
935
- offset: rightPx.offset,
936
- }
937
- if (startOffset && endOffset) {
938
- this.moveTo(startOffset, endOffset)
939
- } else {
940
- const session = getSession(self)
941
- session.notify('No regions found to navigate to', 'warning')
942
- }
943
- },
944
- /**
945
- * Helper method for the fetchSequence.
946
- * Retrieves the corresponding regions that were selected by the rubberband
947
- *
948
- * @param leftOffset - `object as {start, end, index, offset}`, offset = start of user drag
949
- * @param rightOffset - `object as {start, end, index, offset}`, offset = end of user drag
950
- * @returns array of Region[]
951
- */
952
- getSelectedRegions(
953
- leftOffset: BpOffset | undefined,
954
- rightOffset: BpOffset | undefined,
955
- ) {
956
- const simView = Base1DView.create({
957
- ...getSnapshot(self),
958
- interRegionPaddingWidth: self.interRegionPaddingWidth,
959
- })
960
-
961
- simView.setVolatileWidth(self.width)
962
- simView.zoomToDisplayedRegions(leftOffset, rightOffset)
963
-
964
- return simView.dynamicBlocks.contentBlocks.map(region => {
965
- return {
966
- ...region,
967
- start: Math.floor(region.start),
968
- end: Math.ceil(region.end),
969
- }
970
- })
971
- },
972
-
973
- // schedule something to be run after the next time displayedRegions is set
974
- afterDisplayedRegionsSet(cb: Function) {
975
- self.afterDisplayedRegionsSetCallbacks.push(cb)
976
- },
977
- /**
978
- * offset is the base-pair-offset in the displayed region, index is the index of the
979
- * displayed region in the linear genome view
980
- *
981
- * @param start - object as `{start, end, offset, index}`
982
- * @param end - object as `{start, end, offset, index}`
983
- */
984
- moveTo(start: BpOffset, end: BpOffset) {
985
- // find locations in the modellist
986
- let bpSoFar = 0
987
-
988
- if (start.index === end.index) {
989
- bpSoFar += end.offset - start.offset
990
- } else {
991
- const s = self.displayedRegions[start.index]
992
- bpSoFar += s.end - s.start - start.offset
993
- if (end.index - start.index >= 2) {
994
- for (let i = start.index + 1; i < end.index; i += 1) {
995
- bpSoFar +=
996
- self.displayedRegions[i].end - self.displayedRegions[i].start
997
- }
998
- }
999
- bpSoFar += end.offset
1000
- }
1001
- const targetBpPerPx =
1002
- bpSoFar /
1003
- (self.width -
1004
- self.interRegionPaddingWidth * (end.index - start.index))
1005
- const newBpPerPx = self.zoomTo(targetBpPerPx)
1006
- // If our target bpPerPx was smaller than the allowed minBpPerPx, adjust
1007
- // the scroll so the requested range is in the middle of the screen
1008
- let extraBp = 0
1009
- if (targetBpPerPx < newBpPerPx) {
1010
- extraBp = ((newBpPerPx - targetBpPerPx) * self.width) / 2
1011
- }
1012
-
1013
- let bpToStart = -extraBp
1014
- for (let i = 0; i < self.displayedRegions.length; i += 1) {
1015
- const region = self.displayedRegions[i]
1016
- if (start.index === i) {
1017
- bpToStart += start.offset
1018
- break
1019
- } else {
1020
- bpToStart += region.end - region.start
1021
- }
1022
- }
1023
- self.scrollTo(
1024
- Math.round(bpToStart / self.bpPerPx) +
1025
- self.interRegionPaddingWidth * start.index,
1026
- )
1027
- },
1028
-
1029
- horizontalScroll(distance: number) {
1030
- const oldOffsetPx = self.offsetPx
1031
- // newOffsetPx is the actual offset after the scroll is clamped
1032
- const newOffsetPx = self.scrollTo(self.offsetPx + distance)
1033
- return newOffsetPx - oldOffsetPx
1034
- },
1035
-
1036
- /**
1037
- * scrolls the view to center on the given bp. if that is not in any
1038
- * of the displayed regions, does nothing
1039
- * @param bp - basepair at which you want to center the view
1040
- * @param refName - refName of the displayedRegion you are centering at
1041
- * @param regionIndex - index of the displayedRegion
1042
- */
1043
- centerAt(bp: number, refName: string, regionIndex: number) {
1044
- const centerPx = self.bpToPx({
1045
- refName,
1046
- coord: bp,
1047
- regionNumber: regionIndex,
1048
- })
1049
- if (centerPx) {
1050
- self.scrollTo(Math.round(centerPx.offsetPx - self.width / 2))
1051
- }
1052
- },
1053
-
1054
- center() {
1055
- const centerBp = self.totalBp / 2
1056
- self.scrollTo(Math.round(centerBp / self.bpPerPx - self.width / 2))
1057
- },
1058
-
1059
- showAllRegions() {
1060
- self.zoomTo(self.maxBpPerPx)
1061
- this.center()
1062
- },
1063
-
1064
- showAllRegionsInAssembly(assemblyName?: string) {
1065
- const session = getSession(self)
1066
- const { assemblyManager } = session
1067
- if (!assemblyName) {
1068
- const assemblyNames = [
1069
- ...new Set(
1070
- self.displayedRegions.map(region => region.assemblyName),
1071
- ),
1072
- ]
1073
- if (assemblyNames.length > 1) {
1074
- session.notify(
1075
- `Can't perform this with multiple assemblies currently`,
1076
- )
1077
- return
1078
- }
1079
-
1080
- ;[assemblyName] = assemblyNames
1081
- }
1082
- const assembly = assemblyManager.get(assemblyName)
1083
- if (assembly) {
1084
- const { regions } = assembly
1085
- if (regions) {
1086
- this.setDisplayedRegions(regions)
1087
- self.zoomTo(self.maxBpPerPx)
1088
- this.center()
1089
- }
1090
- }
1091
- },
629
+ },
1092
630
 
1093
631
  setDraggingTrackId(idx?: string) {
1094
632
  self.draggingTrackId = idx
@@ -1235,6 +773,13 @@ export function stateModelFactory(pluginManager: PluginManager) {
1235
773
  checked: !self.hideNoTracksActive,
1236
774
  onClick: self.toggleNoTracksActive,
1237
775
  },
776
+ {
777
+ label: 'Show gridlines',
778
+ icon: VisibilityIcon,
779
+ type: 'checkbox',
780
+ checked: self.showGridlines,
781
+ onClick: self.toggleShowGridlines,
782
+ },
1238
783
  {
1239
784
  label: 'Track labels',
1240
785
  icon: LabelIcon,
@@ -1361,6 +906,249 @@ export function stateModelFactory(pluginManager: PluginManager) {
1361
906
  const blob = new Blob([html], { type: 'image/svg+xml' })
1362
907
  saveAs(blob, opts.filename || 'image.svg')
1363
908
  },
909
+ /**
910
+ * offset is the base-pair-offset in the displayed region, index is the index of the
911
+ * displayed region in the linear genome view
912
+ *
913
+ * @param start - object as `{start, end, offset, index}`
914
+ * @param end - object as `{start, end, offset, index}`
915
+ */
916
+ moveTo(start?: BpOffset, end?: BpOffset) {
917
+ moveTo(self, start, end)
918
+ },
919
+
920
+ navToLocString(locString: string, optAssemblyName?: string) {
921
+ const { assemblyNames } = self
922
+ const { assemblyManager } = getSession(self)
923
+ const { isValidRefName } = assemblyManager
924
+ const assemblyName = optAssemblyName || assemblyNames[0]
925
+ let parsedLocStrings
926
+ const inputs = locString
927
+ .split(/(\s+)/)
928
+ .map(f => f.trim())
929
+ .filter(f => !!f)
930
+
931
+ // first try interpreting as a whitespace-separated sequence of
932
+ // multiple locstrings
933
+ try {
934
+ parsedLocStrings = inputs.map(l =>
935
+ parseLocString(l, ref => isValidRefName(ref, assemblyName)),
936
+ )
937
+ } catch (e) {
938
+ // if this fails, try interpreting as a whitespace-separated refname,
939
+ // start, end if start and end are integer inputs
940
+ const [refName, start, end] = inputs
941
+ if (
942
+ `${e}`.match(/Unknown reference sequence/) &&
943
+ Number.isInteger(+start) &&
944
+ Number.isInteger(+end)
945
+ ) {
946
+ parsedLocStrings = [
947
+ parseLocString(refName + ':' + start + '..' + end, ref =>
948
+ isValidRefName(ref, assemblyName),
949
+ ),
950
+ ]
951
+ } else {
952
+ throw e
953
+ }
954
+ }
955
+
956
+ const locations = parsedLocStrings?.map(region => {
957
+ const asmName = region.assemblyName || assemblyName
958
+ const asm = assemblyManager.get(asmName)
959
+ const { refName } = region
960
+ if (!asm) {
961
+ throw new Error(`assembly ${asmName} not found`)
962
+ }
963
+ const { regions } = asm
964
+ if (!regions) {
965
+ throw new Error(`regions not loaded yet for ${asmName}`)
966
+ }
967
+ const canonicalRefName = asm.getCanonicalRefName(region.refName)
968
+ if (!canonicalRefName) {
969
+ throw new Error(`Could not find refName ${refName} in ${asm.name}`)
970
+ }
971
+ const parentRegion = regions.find(
972
+ region => region.refName === canonicalRefName,
973
+ )
974
+ if (!parentRegion) {
975
+ throw new Error(`Could not find refName ${refName} in ${asmName}`)
976
+ }
977
+
978
+ return {
979
+ ...region,
980
+ assemblyName: asmName,
981
+ parentRegion,
982
+ }
983
+ })
984
+
985
+ if (locations.length === 1) {
986
+ const loc = locations[0]
987
+ self.setDisplayedRegions([
988
+ { reversed: loc.reversed, ...loc.parentRegion },
989
+ ])
990
+ const { start, end, parentRegion } = loc
991
+
992
+ this.navTo({
993
+ ...loc,
994
+ start: clamp(start ?? 0, 0, parentRegion.end),
995
+ end: clamp(end ?? parentRegion.end, 0, parentRegion.end),
996
+ })
997
+ } else {
998
+ self.setDisplayedRegions(
999
+ // @ts-ignore
1000
+ locations.map(r => (r.start === undefined ? r.parentRegion : r)),
1001
+ )
1002
+ self.showAllRegions()
1003
+ }
1004
+ },
1005
+
1006
+ /**
1007
+ * Navigate to a location based on its refName and optionally start, end,
1008
+ * and assemblyName. Can handle if there are multiple displayedRegions
1009
+ * from same refName. Only navigates to a location if it is entirely
1010
+ * within a displayedRegion. Navigates to the first matching location
1011
+ * encountered.
1012
+ *
1013
+ * Throws an error if navigation was unsuccessful
1014
+ *
1015
+ * @param location - a proposed location to navigate to
1016
+ */
1017
+ navTo(query: NavLocation) {
1018
+ this.navToMultiple([query])
1019
+ },
1020
+
1021
+ navToMultiple(locations: NavLocation[]) {
1022
+ const firstLocation = locations[0]
1023
+ let { refName } = firstLocation
1024
+ const {
1025
+ start,
1026
+ end,
1027
+ assemblyName = self.assemblyNames[0],
1028
+ } = firstLocation
1029
+
1030
+ if (start !== undefined && end !== undefined && start > end) {
1031
+ throw new Error(`start "${start + 1}" is greater than end "${end}"`)
1032
+ }
1033
+ const session = getSession(self)
1034
+ const { assemblyManager } = session
1035
+ const assembly = assemblyManager.get(assemblyName)
1036
+ if (assembly) {
1037
+ const canonicalRefName = assembly.getCanonicalRefName(refName)
1038
+ if (canonicalRefName) {
1039
+ refName = canonicalRefName
1040
+ }
1041
+ }
1042
+ let s = start
1043
+ let e = end
1044
+ let refNameMatched = false
1045
+ const predicate = (r: Region) => {
1046
+ if (refName === r.refName) {
1047
+ refNameMatched = true
1048
+ if (s === undefined) {
1049
+ s = r.start
1050
+ }
1051
+ if (e === undefined) {
1052
+ e = r.end
1053
+ }
1054
+ if (s >= r.start && s <= r.end && e <= r.end && e >= r.start) {
1055
+ return true
1056
+ }
1057
+ s = start
1058
+ e = end
1059
+ }
1060
+ return false
1061
+ }
1062
+
1063
+ const lastIndex = findLastIndex(self.displayedRegions, predicate)
1064
+ let index
1065
+ while (index !== lastIndex) {
1066
+ try {
1067
+ const previousIndex: number | undefined = index
1068
+ index = self.displayedRegions
1069
+ .slice(previousIndex === undefined ? 0 : previousIndex + 1)
1070
+ .findIndex(predicate)
1071
+ if (previousIndex !== undefined) {
1072
+ index += previousIndex + 1
1073
+ }
1074
+ if (!refNameMatched) {
1075
+ throw new Error(
1076
+ `could not find a region with refName "${refName}"`,
1077
+ )
1078
+ }
1079
+ if (s === undefined) {
1080
+ throw new Error(
1081
+ `could not find a region with refName "${refName}" that contained an end position ${e}`,
1082
+ )
1083
+ }
1084
+ if (e === undefined) {
1085
+ throw new Error(
1086
+ `could not find a region with refName "${refName}" that contained a start position ${
1087
+ s + 1
1088
+ }`,
1089
+ )
1090
+ }
1091
+ if (index === -1) {
1092
+ throw new Error(
1093
+ `could not find a region that completely contained "${assembleLocString(
1094
+ firstLocation,
1095
+ )}"`,
1096
+ )
1097
+ }
1098
+ if (locations.length === 1) {
1099
+ const f = self.displayedRegions[index]
1100
+ this.moveTo(
1101
+ { index, offset: f.reversed ? f.end - e : s - f.start },
1102
+ { index, offset: f.reversed ? f.end - s : e - f.start },
1103
+ )
1104
+ return
1105
+ }
1106
+ let locationIndex = 0
1107
+ let locationStart = 0
1108
+ let locationEnd = 0
1109
+ for (
1110
+ locationIndex;
1111
+ locationIndex < locations.length;
1112
+ locationIndex++
1113
+ ) {
1114
+ const location = locations[locationIndex]
1115
+ const region = self.displayedRegions[index + locationIndex]
1116
+ locationStart = location.start || region.start
1117
+ locationEnd = location.end || region.end
1118
+ if (location.refName !== region.refName) {
1119
+ throw new Error(
1120
+ `Entered location ${assembleLocString(
1121
+ location,
1122
+ )} does not match with displayed regions`,
1123
+ )
1124
+ }
1125
+ }
1126
+ locationIndex -= 1
1127
+ const startDisplayedRegion = self.displayedRegions[index]
1128
+ const endDisplayedRegion =
1129
+ self.displayedRegions[index + locationIndex]
1130
+ this.moveTo(
1131
+ {
1132
+ index,
1133
+ offset: startDisplayedRegion.reversed
1134
+ ? startDisplayedRegion.end - e
1135
+ : s - startDisplayedRegion.start,
1136
+ },
1137
+ {
1138
+ index: index + locationIndex,
1139
+ offset: endDisplayedRegion.reversed
1140
+ ? endDisplayedRegion.end - locationStart
1141
+ : locationEnd - endDisplayedRegion.start,
1142
+ },
1143
+ )
1144
+ return
1145
+ } catch (error) {
1146
+ if (index === lastIndex) {
1147
+ throw error
1148
+ }
1149
+ }
1150
+ }
1151
+ },
1364
1152
  }))
1365
1153
  .views(self => ({
1366
1154
  rubberBandMenuItems(): MenuItem[] {
@@ -1370,9 +1158,7 @@ export function stateModelFactory(pluginManager: PluginManager) {
1370
1158
  icon: ZoomInIcon,
1371
1159
  onClick: () => {
1372
1160
  const { leftOffset, rightOffset } = self
1373
- if (leftOffset && rightOffset) {
1374
- self.moveTo(leftOffset, rightOffset)
1375
- }
1161
+ self.moveTo(leftOffset, rightOffset)
1376
1162
  },
1377
1163
  },
1378
1164
  {
@@ -1384,6 +1170,46 @@ export function stateModelFactory(pluginManager: PluginManager) {
1384
1170
  },
1385
1171
  ]
1386
1172
  },
1173
+
1174
+ bpToPx({
1175
+ refName,
1176
+ coord,
1177
+ regionNumber,
1178
+ }: {
1179
+ refName: string
1180
+ coord: number
1181
+ regionNumber?: number
1182
+ }) {
1183
+ return bpToPx({ refName, coord, regionNumber, self })
1184
+ },
1185
+
1186
+ /**
1187
+ * scrolls the view to center on the given bp. if that is not in any
1188
+ * of the displayed regions, does nothing
1189
+ * @param coord - basepair at which you want to center the view
1190
+ * @param refName - refName of the displayedRegion you are centering at
1191
+ * @param regionNumber - index of the displayedRegion
1192
+ */
1193
+ centerAt(coord: number, refName: string, regionNumber: number) {
1194
+ const centerPx = this.bpToPx({
1195
+ refName,
1196
+ coord,
1197
+ regionNumber,
1198
+ })
1199
+ if (centerPx) {
1200
+ self.scrollTo(Math.round(centerPx.offsetPx - self.width / 2))
1201
+ }
1202
+ },
1203
+
1204
+ pxToBp(px: number) {
1205
+ return pxToBp(self, px)
1206
+ },
1207
+
1208
+ get centerLineInfo() {
1209
+ return self.displayedRegions.length
1210
+ ? this.pxToBp(self.width / 2)
1211
+ : undefined
1212
+ },
1387
1213
  }))
1388
1214
  }
1389
1215