@jbrowse/plugin-linear-genome-view 1.5.8 → 1.6.2

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 (28) hide show
  1. package/dist/BaseLinearDisplay/models/BaseLinearDisplayModel.d.ts +35 -17
  2. package/dist/LinearBareDisplay/model.d.ts +28 -4
  3. package/dist/LinearBasicDisplay/model.d.ts +28 -4
  4. package/dist/LinearGenomeView/components/Header.d.ts +2 -1
  5. package/dist/LinearGenomeView/components/RefNameAutocomplete.d.ts +2 -1
  6. package/dist/LinearGenomeView/components/ScaleBar.d.ts +8 -8
  7. package/dist/LinearGenomeView/components/SearchBox.d.ts +8 -0
  8. package/dist/LinearGenomeView/index.d.ts +7 -4
  9. package/dist/index.d.ts +85 -14
  10. package/dist/plugin-linear-genome-view.cjs.development.js +738 -389
  11. package/dist/plugin-linear-genome-view.cjs.development.js.map +1 -1
  12. package/dist/plugin-linear-genome-view.cjs.production.min.js +1 -1
  13. package/dist/plugin-linear-genome-view.cjs.production.min.js.map +1 -1
  14. package/dist/plugin-linear-genome-view.esm.js +749 -401
  15. package/dist/plugin-linear-genome-view.esm.js.map +1 -1
  16. package/package.json +2 -2
  17. package/src/BaseLinearDisplay/models/BaseLinearDisplayModel.tsx +308 -88
  18. package/src/BaseLinearDisplay/models/baseLinearDisplayConfigSchema.ts +10 -3
  19. package/src/LinearBasicDisplay/configSchema.ts +0 -6
  20. package/src/LinearGenomeView/components/Header.tsx +38 -120
  21. package/src/LinearGenomeView/components/ImportForm.tsx +1 -1
  22. package/src/LinearGenomeView/components/LinearGenomeView.test.js +6 -4
  23. package/src/LinearGenomeView/components/RefNameAutocomplete.tsx +9 -5
  24. package/src/LinearGenomeView/components/SearchBox.tsx +111 -0
  25. package/src/LinearGenomeView/components/TrackLabel.tsx +10 -6
  26. package/src/LinearGenomeView/components/__snapshots__/LinearGenomeView.test.js.snap +10 -11
  27. package/src/LinearGenomeView/index.tsx +25 -53
  28. package/src/index.ts +61 -2
@@ -1,16 +1,13 @@
1
1
  import React from 'react'
2
2
  import { observer } from 'mobx-react'
3
- import { getSession } from '@jbrowse/core/util'
4
3
  import {
5
4
  Button,
6
5
  FormGroup,
7
6
  Typography,
8
7
  makeStyles,
9
- useTheme,
10
8
  alpha,
11
9
  } from '@material-ui/core'
12
- import BaseResult from '@jbrowse/core/TextSearch/BaseResults'
13
- import { SearchType } from '@jbrowse/core/data_adapters/BaseAdapter'
10
+ import SearchBox from './SearchBox'
14
11
 
15
12
  // icons
16
13
  import { TrackSelector as TrackSelectorIcon } from '@jbrowse/core/ui/Icons'
@@ -18,15 +15,16 @@ import ArrowForwardIcon from '@material-ui/icons/ArrowForward'
18
15
  import ArrowBackIcon from '@material-ui/icons/ArrowBack'
19
16
 
20
17
  // locals
21
- import { LinearGenomeViewModel, HEADER_BAR_HEIGHT } from '..'
22
- import RefNameAutocomplete from './RefNameAutocomplete'
18
+ import {
19
+ LinearGenomeViewModel,
20
+ SPACING,
21
+ WIDGET_HEIGHT,
22
+ HEADER_BAR_HEIGHT,
23
+ } from '..'
23
24
  import OverviewScaleBar from './OverviewScaleBar'
24
25
  import ZoomControls from './ZoomControls'
25
- import { dedupe } from './util'
26
-
27
- const WIDGET_HEIGHT = 32
28
- const SPACING = 7
29
26
 
27
+ type LGV = LinearGenomeViewModel
30
28
  const useStyles = makeStyles(theme => ({
31
29
  headerBar: {
32
30
  height: HEADER_BAR_HEIGHT,
@@ -39,10 +37,7 @@ const useStyles = makeStyles(theme => ({
39
37
  spacer: {
40
38
  flexGrow: 1,
41
39
  },
42
- input: {},
43
- headerRefName: {
44
- minWidth: 100,
45
- },
40
+
46
41
  panButton: {
47
42
  background: alpha(theme.palette.background.paper, 0.8),
48
43
  height: WIDGET_HEIGHT,
@@ -63,7 +58,7 @@ const useStyles = makeStyles(theme => ({
63
58
  },
64
59
  }))
65
60
 
66
- const Controls = observer(({ model }: { model: LinearGenomeViewModel }) => {
61
+ const HeaderButtons = observer(({ model }: { model: LGV }) => {
67
62
  const classes = useStyles()
68
63
  return (
69
64
  <Button
@@ -78,7 +73,7 @@ const Controls = observer(({ model }: { model: LinearGenomeViewModel }) => {
78
73
  )
79
74
  })
80
75
 
81
- function PanControls({ model }: { model: LinearGenomeViewModel }) {
76
+ function PanControls({ model }: { model: LGV }) {
82
77
  const classes = useStyles()
83
78
  return (
84
79
  <>
@@ -100,118 +95,41 @@ function PanControls({ model }: { model: LinearGenomeViewModel }) {
100
95
  )
101
96
  }
102
97
 
103
- const RegionWidth = observer(({ model }: { model: LinearGenomeViewModel }) => {
98
+ const RegionWidth = observer(({ model }: { model: LGV }) => {
104
99
  const classes = useStyles()
105
100
  const { coarseTotalBp } = model
106
101
  return (
107
102
  <Typography variant="body2" color="textSecondary" className={classes.bp}>
108
- {`${Math.round(coarseTotalBp).toLocaleString('en-US')} bp`}
103
+ {Math.round(coarseTotalBp).toLocaleString('en-US')} bp
109
104
  </Typography>
110
105
  )
111
106
  })
112
107
 
113
- const LinearGenomeViewHeader = observer(
114
- ({ model }: { model: LinearGenomeViewModel }) => {
115
- const classes = useStyles()
116
- const theme = useTheme()
117
- const session = getSession(model)
118
-
119
- const { textSearchManager, assemblyManager } = session
120
- const { assemblyNames, rankSearchResults } = model
121
- const assemblyName = assemblyNames[0]
122
- const assembly = assemblyManager.get(assemblyName)
123
- const searchScope = model.searchScope(assemblyName)
124
-
125
- async function fetchResults(query: string, searchType?: SearchType) {
126
- if (!textSearchManager) {
127
- console.warn('No text search manager')
128
- }
129
-
130
- const textSearchResults = await textSearchManager?.search(
131
- {
132
- queryString: query,
133
- searchType,
134
- },
135
- searchScope,
136
- rankSearchResults,
137
- )
138
-
139
- const refNameResults = assembly?.allRefNames
140
- ?.filter(refName => refName.startsWith(query))
141
- .map(r => new BaseResult({ label: r }))
142
- .slice(0, 10)
143
-
144
- return dedupe(
145
- [...(refNameResults || []), ...(textSearchResults || [])],
146
- elt => elt.getId(),
147
- )
148
- }
149
-
150
- async function handleSelectedRegion(option: BaseResult) {
151
- let trackId = option.getTrackId()
152
- let location = option.getLocation()
153
- const label = option.getLabel()
154
- try {
155
- if (assembly?.allRefNames?.includes(location)) {
156
- model.navToLocString(location)
157
- } else {
158
- const results = await fetchResults(label, 'exact')
159
- if (results && results.length > 1) {
160
- model.setSearchResults(results, label.toLowerCase())
161
- return
162
- } else if (results?.length === 1) {
163
- location = results[0].getLocation()
164
- trackId = results[0].getTrackId()
165
- }
166
-
167
- model.navToLocString(location, assemblyName)
168
- if (trackId) {
169
- model.showTrack(trackId)
170
- }
171
- }
172
- } catch (e) {
173
- console.error(e)
174
- session.notify(`${e}`, 'warning')
175
- }
176
- }
177
-
178
- const controls = (
179
- <div className={classes.headerBar}>
180
- <Controls model={model} />
181
- <div className={classes.spacer} />
182
- <FormGroup row className={classes.headerForm}>
183
- <PanControls model={model} />
184
- <RefNameAutocomplete
185
- onSelect={handleSelectedRegion}
186
- assemblyName={assemblyName}
187
- fetchResults={fetchResults}
188
- model={model}
189
- TextFieldProps={{
190
- variant: 'outlined',
191
- className: classes.headerRefName,
192
- style: { margin: SPACING, minWidth: '175px' },
193
- InputProps: {
194
- style: {
195
- padding: 0,
196
- height: WIDGET_HEIGHT,
197
- background: alpha(theme.palette.background.paper, 0.8),
198
- },
199
- },
200
- }}
201
- />
202
- </FormGroup>
203
- <RegionWidth model={model} />
204
- <ZoomControls model={model} />
205
- <div className={classes.spacer} />
206
- </div>
207
- )
208
-
209
- if (model.hideHeaderOverview) {
210
- return controls
211
- }
108
+ const Controls = ({ model }: { model: LGV }) => {
109
+ const classes = useStyles()
110
+ return (
111
+ <div className={classes.headerBar}>
112
+ <HeaderButtons model={model} />
113
+ <div className={classes.spacer} />
114
+ <FormGroup row className={classes.headerForm}>
115
+ <PanControls model={model} />
116
+ <SearchBox model={model} />
117
+ </FormGroup>
118
+ <RegionWidth model={model} />
119
+ <ZoomControls model={model} />
120
+ <div className={classes.spacer} />
121
+ </div>
122
+ )
123
+ }
212
124
 
213
- return <OverviewScaleBar model={model}>{controls}</OverviewScaleBar>
214
- },
215
- )
125
+ const LinearGenomeViewHeader = observer(({ model }: { model: LGV }) => {
126
+ return model.hideHeaderOverview ? (
127
+ <Controls model={model} />
128
+ ) : (
129
+ <OverviewScaleBar model={model}>
130
+ <Controls model={model} />
131
+ </OverviewScaleBar>
132
+ )
133
+ })
216
134
 
217
135
  export default LinearGenomeViewHeader
@@ -9,9 +9,9 @@ import {
9
9
  makeStyles,
10
10
  } from '@material-ui/core'
11
11
  import { SearchType } from '@jbrowse/core/data_adapters/BaseAdapter'
12
+ import ErrorMessage from '@jbrowse/core/ui/ErrorMessage'
12
13
  import BaseResult from '@jbrowse/core/TextSearch/BaseResults'
13
14
  import AssemblySelector from '@jbrowse/core/ui/AssemblySelector'
14
- import ErrorMessage from '@jbrowse/core/ui/ErrorMessage'
15
15
  import CloseIcon from '@material-ui/icons/Close'
16
16
 
17
17
  // locals
@@ -1,12 +1,10 @@
1
1
  import React from 'react'
2
2
  import { fireEvent, render, waitFor } from '@testing-library/react'
3
+ import '@testing-library/jest-dom/extend-expect'
3
4
  import { createTestSession } from '@jbrowse/web/src/rootModel'
4
- import sizeMe from 'react-sizeme'
5
5
  import 'requestidlecallback-polyfill'
6
6
  import LinearGenomeView from './LinearGenomeView'
7
7
 
8
- sizeMe.noPlaceholders = true
9
-
10
8
  const assemblyConf = {
11
9
  name: 'volMyt1',
12
10
  sequence: {
@@ -142,9 +140,13 @@ describe('<LinearGenomeView />', () => {
142
140
  })
143
141
  const model = session.views[0]
144
142
  model.setWidth(800)
145
- const { container, findByText } = render(<LinearGenomeView model={model} />)
143
+ const { container, findByText, findAllByTestId } = render(
144
+ <LinearGenomeView model={model} />,
145
+ )
146
146
  await findByText('Foo Track')
147
147
  await findByText('798 bp')
148
+ await findAllByTestId('svgfeatures')
149
+
148
150
  expect(container.firstChild).toMatchSnapshot()
149
151
  })
150
152
  })
@@ -66,6 +66,7 @@ const MyPopper = function (
66
66
 
67
67
  function RefNameAutocomplete({
68
68
  model,
69
+ showHelp = true,
69
70
  onSelect,
70
71
  assemblyName,
71
72
  style,
@@ -81,6 +82,7 @@ function RefNameAutocomplete({
81
82
  fetchResults: (query: string) => Promise<BaseResult[]>
82
83
  style?: React.CSSProperties
83
84
  minWidth?: number
85
+ showHelp?: boolean
84
86
  TextFieldProps?: TFP
85
87
  }) {
86
88
  const session = getSession(model)
@@ -238,11 +240,13 @@ function RefNameAutocomplete({
238
240
  ) : (
239
241
  <InputAdornment position="end" style={{ marginRight: 7 }}>
240
242
  <SearchIcon />
241
- <IconButton
242
- onClick={() => setHelpDialogDisplayed(true)}
243
- >
244
- <HelpIcon />
245
- </IconButton>
243
+ {showHelp ? (
244
+ <IconButton
245
+ onClick={() => setHelpDialogDisplayed(true)}
246
+ >
247
+ <HelpIcon />
248
+ </IconButton>
249
+ ) : null}
246
250
  </InputAdornment>
247
251
  )}
248
252
  {params.InputProps.endAdornment}
@@ -0,0 +1,111 @@
1
+ import React from 'react'
2
+ import { observer } from 'mobx-react'
3
+ import { makeStyles, useTheme, alpha } from '@material-ui/core'
4
+ import BaseResult from '@jbrowse/core/TextSearch/BaseResults'
5
+ import { getSession } from '@jbrowse/core/util'
6
+ import { SearchType } from '@jbrowse/core/data_adapters/BaseAdapter'
7
+
8
+ // locals
9
+ import RefNameAutocomplete from './RefNameAutocomplete'
10
+ import { dedupe } from './util'
11
+ import { LinearGenomeViewModel, SPACING, WIDGET_HEIGHT } from '..'
12
+
13
+ const useStyles = makeStyles(() => ({
14
+ headerRefName: {
15
+ minWidth: 100,
16
+ },
17
+ }))
18
+
19
+ function SearchBox({
20
+ model,
21
+ showHelp,
22
+ }: {
23
+ showHelp?: boolean
24
+ model: LinearGenomeViewModel
25
+ }) {
26
+ const classes = useStyles()
27
+ const theme = useTheme()
28
+ const session = getSession(model)
29
+
30
+ const { textSearchManager, assemblyManager } = session
31
+ const { assemblyNames, rankSearchResults } = model
32
+ const assemblyName = assemblyNames[0]
33
+ const assembly = assemblyManager.get(assemblyName)
34
+ const searchScope = model.searchScope(assemblyName)
35
+
36
+ async function fetchResults(query: string, searchType?: SearchType) {
37
+ if (!textSearchManager) {
38
+ console.warn('No text search manager')
39
+ }
40
+
41
+ const textSearchResults = await textSearchManager?.search(
42
+ {
43
+ queryString: query,
44
+ searchType,
45
+ },
46
+ searchScope,
47
+ rankSearchResults,
48
+ )
49
+
50
+ const refNameResults = assembly?.allRefNames
51
+ ?.filter(refName => refName.startsWith(query))
52
+ .map(r => new BaseResult({ label: r }))
53
+ .slice(0, 10)
54
+
55
+ return dedupe(
56
+ [...(refNameResults || []), ...(textSearchResults || [])],
57
+ elt => elt.getId(),
58
+ )
59
+ }
60
+
61
+ async function handleSelectedRegion(option: BaseResult) {
62
+ let trackId = option.getTrackId()
63
+ let location = option.getLocation()
64
+ const label = option.getLabel()
65
+ try {
66
+ if (assembly?.allRefNames?.includes(location)) {
67
+ model.navToLocString(location)
68
+ } else {
69
+ const results = await fetchResults(label, 'exact')
70
+ if (results && results.length > 1) {
71
+ model.setSearchResults(results, label.toLowerCase())
72
+ return
73
+ } else if (results?.length === 1) {
74
+ location = results[0].getLocation()
75
+ trackId = results[0].getTrackId()
76
+ }
77
+
78
+ model.navToLocString(location, assemblyName)
79
+ if (trackId) {
80
+ model.showTrack(trackId)
81
+ }
82
+ }
83
+ } catch (e) {
84
+ console.error(e)
85
+ session.notify(`${e}`, 'warning')
86
+ }
87
+ }
88
+ return (
89
+ <RefNameAutocomplete
90
+ showHelp={showHelp}
91
+ onSelect={handleSelectedRegion}
92
+ assemblyName={assemblyName}
93
+ fetchResults={fetchResults}
94
+ model={model}
95
+ TextFieldProps={{
96
+ variant: 'outlined',
97
+ className: classes.headerRefName,
98
+ style: { margin: SPACING, minWidth: '175px' },
99
+ InputProps: {
100
+ style: {
101
+ padding: 0,
102
+ height: WIDGET_HEIGHT,
103
+ background: alpha(theme.palette.background.paper, 0.8),
104
+ },
105
+ },
106
+ }}
107
+ />
108
+ )
109
+ }
110
+
111
+ export default observer(SearchBox)
@@ -1,12 +1,17 @@
1
+ import React from 'react'
1
2
  import { getConf, readConfObject } from '@jbrowse/core/configuration'
2
3
  import { Menu } from '@jbrowse/core/ui'
3
4
  import { getSession, getContainingView } from '@jbrowse/core/util'
4
5
  import { BaseTrackModel } from '@jbrowse/core/pluggableElementTypes/models'
5
- import IconButton from '@material-ui/core/IconButton'
6
- import Paper from '@material-ui/core/Paper'
7
- import { makeStyles } from '@material-ui/core/styles'
8
- import { alpha } from '@material-ui/core/styles'
9
- import Typography from '@material-ui/core/Typography'
6
+ import {
7
+ IconButton,
8
+ Paper,
9
+ Typography,
10
+ alpha,
11
+ makeStyles,
12
+ } from '@material-ui/core'
13
+
14
+ // icons
10
15
  import MoreVertIcon from '@material-ui/icons/MoreVert'
11
16
  import DragIcon from '@material-ui/icons/DragIndicator'
12
17
  import CloseIcon from '@material-ui/icons/Close'
@@ -14,7 +19,6 @@ import CloseIcon from '@material-ui/icons/Close'
14
19
  import clsx from 'clsx'
15
20
  import { observer } from 'mobx-react'
16
21
  import { Instance } from 'mobx-state-tree'
17
- import React from 'react'
18
22
  import { LinearGenomeViewStateModel } from '..'
19
23
 
20
24
  const useStyles = makeStyles(theme => ({
@@ -255,7 +255,8 @@ exports[`<LinearGenomeView /> renders one track, one region 1`] = `
255
255
  <p
256
256
  class="MuiTypography-root makeStyles-bp MuiTypography-body2 MuiTypography-colorTextSecondary"
257
257
  >
258
- 100 bp
258
+ 100
259
+ bp
259
260
  </p>
260
261
  <div
261
262
  class="makeStyles-container"
@@ -555,15 +556,8 @@ exports[`<LinearGenomeView /> renders one track, one region 1`] = `
555
556
  style="width: 100px;"
556
557
  >
557
558
  <div
558
- style="position: relative;"
559
- >
560
- <svg
561
- class="SvgFeatureRendering"
562
- height="100"
563
- style="display: block;"
564
- width="100"
565
- />
566
- </div>
559
+ style="display: flex;"
560
+ />
567
561
  </div>
568
562
  <div
569
563
  class="makeStyles-boundaryPaddingBlock"
@@ -858,7 +852,8 @@ exports[`<LinearGenomeView /> renders two tracks, two regions 1`] = `
858
852
  <p
859
853
  class="MuiTypography-root makeStyles-bp MuiTypography-body2 MuiTypography-colorTextSecondary"
860
854
  >
861
- 798 bp
855
+ 798
856
+ bp
862
857
  </p>
863
858
  <div
864
859
  class="makeStyles-container"
@@ -1457,6 +1452,7 @@ exports[`<LinearGenomeView /> renders two tracks, two regions 1`] = `
1457
1452
  >
1458
1453
  <svg
1459
1454
  class="SvgFeatureRendering"
1455
+ data-testid="svgfeatures"
1460
1456
  height="100"
1461
1457
  style="display: block;"
1462
1458
  width="100"
@@ -1476,6 +1472,7 @@ exports[`<LinearGenomeView /> renders two tracks, two regions 1`] = `
1476
1472
  >
1477
1473
  <svg
1478
1474
  class="SvgFeatureRendering"
1475
+ data-testid="svgfeatures"
1479
1476
  height="100"
1480
1477
  style="display: block;"
1481
1478
  width="1000"
@@ -1609,6 +1606,7 @@ exports[`<LinearGenomeView /> renders two tracks, two regions 1`] = `
1609
1606
  >
1610
1607
  <svg
1611
1608
  class="SvgFeatureRendering"
1609
+ data-testid="svgfeatures"
1612
1610
  height="100"
1613
1611
  style="display: block;"
1614
1612
  width="100"
@@ -1628,6 +1626,7 @@ exports[`<LinearGenomeView /> renders two tracks, two regions 1`] = `
1628
1626
  >
1629
1627
  <svg
1630
1628
  class="SvgFeatureRendering"
1629
+ data-testid="svgfeatures"
1631
1630
  height="100"
1632
1631
  style="display: block;"
1633
1632
  width="1000"
@@ -14,6 +14,7 @@ import {
14
14
  measureText,
15
15
  parseLocString,
16
16
  springAnimate,
17
+ viewBpToPx,
17
18
  } from '@jbrowse/core/util'
18
19
  import BaseResult from '@jbrowse/core/TextSearch/BaseResults'
19
20
  import { BlockSet, BaseBlock } from '@jbrowse/core/util/blockTypes'
@@ -50,6 +51,7 @@ import MenuOpenIcon from '@material-ui/icons/MenuOpen'
50
51
  // locals
51
52
  import { renderToSvg } from './components/LinearGenomeViewSvg'
52
53
  import RefNameAutocomplete from './components/RefNameAutocomplete'
54
+ import SearchBox from './components/SearchBox'
53
55
  import ExportSvgDlg from './components/ExportSvgDialog'
54
56
  import ReturnToImportFormDlg from './components/ReturnToImportFormDialog'
55
57
 
@@ -99,6 +101,8 @@ export const HEADER_OVERVIEW_HEIGHT = 20
99
101
  export const SCALE_BAR_HEIGHT = 17
100
102
  export const RESIZE_HANDLE_HEIGHT = 3
101
103
  export const INTER_REGION_PADDING_WIDTH = 2
104
+ export const WIDGET_HEIGHT = 32
105
+ export const SPACING = 7
102
106
 
103
107
  export function stateModelFactory(pluginManager: PluginManager) {
104
108
  return types
@@ -122,9 +126,18 @@ export function stateModelFactory(pluginManager: PluginManager) {
122
126
  types.enumeration(['hierarchical']),
123
127
  'hierarchical',
124
128
  ),
125
- trackLabels: 'overlapping' as 'overlapping' | 'hidden' | 'offset',
126
- showCenterLine: false,
127
- showCytobandsSetting: true,
129
+ trackLabels: types.optional(
130
+ types.string,
131
+ () => localStorage.getItem('lgv-trackLabels') || 'overlapping',
132
+ ),
133
+ showCenterLine: types.optional(types.boolean, () => {
134
+ const setting = localStorage.getItem('lgv-showCenterLine')
135
+ return setting !== undefined ? !!setting : false
136
+ }),
137
+ showCytobandsSetting: types.optional(types.boolean, () => {
138
+ const setting = localStorage.getItem('lgv-showCytobands')
139
+ return setting !== undefined ? !!setting : true
140
+ }),
128
141
  }),
129
142
  )
130
143
  .volatile(() => ({
@@ -213,11 +226,7 @@ export function stateModelFactory(pluginManager: PluginManager) {
213
226
  )
214
227
  },
215
228
  get totalBp() {
216
- let totalbp = 0
217
- self.displayedRegions.forEach(region => {
218
- totalbp += region.end - region.start
219
- })
220
- return totalbp
229
+ return self.displayedRegions.reduce((a, b) => a + b.end - b.start, 0)
221
230
  },
222
231
 
223
232
  get maxBpPerPx() {
@@ -288,47 +297,7 @@ export function stateModelFactory(pluginManager: PluginManager) {
288
297
  coord: number
289
298
  regionNumber?: number
290
299
  }) {
291
- let offsetBp = 0
292
-
293
- const interRegionPaddingBp = self.interRegionPaddingWidth * self.bpPerPx
294
- const minimumBlockBp = self.minimumBlockWidth * self.bpPerPx
295
- const index = self.displayedRegions.findIndex((region, idx) => {
296
- const len = region.end - region.start
297
- if (
298
- refName === region.refName &&
299
- coord >= region.start &&
300
- coord <= region.end
301
- ) {
302
- if (regionNumber ? regionNumber === idx : true) {
303
- offsetBp += region.reversed
304
- ? region.end - coord
305
- : coord - region.start
306
- return true
307
- }
308
- }
309
-
310
- // add the interRegionPaddingWidth if the boundary is in the screen
311
- // e.g. offset>=0 && offset<width
312
- if (
313
- len > minimumBlockBp &&
314
- offsetBp / self.bpPerPx >= 0 &&
315
- offsetBp / self.bpPerPx < self.width
316
- ) {
317
- offsetBp += len + interRegionPaddingBp
318
- } else {
319
- offsetBp += len
320
- }
321
- return false
322
- })
323
- const foundRegion = self.displayedRegions[index]
324
- if (foundRegion) {
325
- return {
326
- index,
327
- offsetPx: Math.round(offsetBp / self.bpPerPx),
328
- }
329
- }
330
-
331
- return undefined
300
+ return viewBpToPx({ refName, coord, regionNumber, self })
332
301
  },
333
302
  /**
334
303
  *
@@ -474,6 +443,7 @@ export function stateModelFactory(pluginManager: PluginManager) {
474
443
  .actions(self => ({
475
444
  setShowCytobands(flag: boolean) {
476
445
  self.showCytobandsSetting = flag
446
+ localStorage.setItem('lgv-showCytobands', `${+flag}`)
477
447
  },
478
448
  setWidth(newWidth: number) {
479
449
  self.volatileWidth = newWidth
@@ -566,11 +536,11 @@ export function stateModelFactory(pluginManager: PluginManager) {
566
536
  trackId,
567
537
  )
568
538
  if (!configuration) {
569
- throw new Error(`Could not resolve identifier`)
539
+ throw new Error(`Could not resolve identifier "${trackId}"`)
570
540
  }
571
541
  const trackType = pluginManager.getTrackType(configuration?.type)
572
542
  if (!trackType) {
573
- throw new Error(`unknown track type ${configuration.type}`)
543
+ throw new Error(`Unknown track type ${configuration.type}`)
574
544
  }
575
545
  const viewType = pluginManager.getViewType(self.type)
576
546
  const supportedDisplays = viewType.displayTypes.map(
@@ -581,7 +551,7 @@ export function stateModelFactory(pluginManager: PluginManager) {
581
551
  )
582
552
  if (!displayConf) {
583
553
  throw new Error(
584
- `could not find a compatible display for view type ${self.type}`,
554
+ `Could not find a compatible display for view type ${self.type}`,
585
555
  )
586
556
  }
587
557
 
@@ -666,10 +636,12 @@ export function stateModelFactory(pluginManager: PluginManager) {
666
636
 
667
637
  setTrackLabels(setting: 'overlapping' | 'offset' | 'hidden') {
668
638
  self.trackLabels = setting
639
+ localStorage.setItem('lgv-trackLabels', setting)
669
640
  },
670
641
 
671
642
  toggleCenterLine() {
672
643
  self.showCenterLine = !self.showCenterLine
644
+ localStorage.setItem('lgv-showCenterLine', `${+self.showCenterLine}`)
673
645
  },
674
646
 
675
647
  setDisplayedRegions(regions: Region[]) {
@@ -1456,7 +1428,7 @@ export function stateModelFactory(pluginManager: PluginManager) {
1456
1428
  }))
1457
1429
  }
1458
1430
 
1459
- export { renderToSvg, RefNameAutocomplete }
1431
+ export { renderToSvg, RefNameAutocomplete, SearchBox }
1460
1432
  export type LinearGenomeViewStateModel = ReturnType<typeof stateModelFactory>
1461
1433
  export type LinearGenomeViewModel = Instance<LinearGenomeViewStateModel>
1462
1434
  export { default as ReactComponent } from './components/LinearGenomeView'