Skip to content

Commit

Permalink
Allow user to edit region size as 'Custom zoom' (#4789)
Browse files Browse the repository at this point in the history
  • Loading branch information
cmdcolin authored Jan 29, 2025
1 parent ecada63 commit 06c5f9f
Show file tree
Hide file tree
Showing 14 changed files with 344 additions and 95 deletions.
100 changes: 16 additions & 84 deletions plugins/linear-genome-view/src/LinearGenomeView/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
import { TrackSelector as TrackSelectorIcon } from '@jbrowse/core/ui/Icons'
import { getBpDisplayStr } from '@jbrowse/core/util'
import ArrowBackIcon from '@mui/icons-material/ArrowBack'
import ArrowForwardIcon from '@mui/icons-material/ArrowForward'
import { Button, FormGroup, IconButton, Typography, alpha } from '@mui/material'
import { FormGroup } from '@mui/material'
import { observer } from 'mobx-react'
import { makeStyles } from 'tss-react/mui'

import HeaderPanControls from './HeaderPanControls'
import HeaderRegionWidth from './HeaderRegionWidth'
import HeaderTrackSelectorButton from './HeaderTrackSelectorButton'
import HeaderZoomControls from './HeaderZoomControls'
import OverviewScalebar from './OverviewScalebar'
import SearchBox from './SearchBox'
import ZoomControls from './ZoomControls'
import { SPACING } from '../consts'

import type { LinearGenomeViewModel } from '..'

type LGV = LinearGenomeViewModel
const useStyles = makeStyles()(theme => ({
const useStyles = makeStyles()({
headerBar: {
display: 'flex',
},
Expand All @@ -25,95 +22,30 @@ const useStyles = makeStyles()(theme => ({
spacer: {
flexGrow: 1,
},

panButton: {
background: alpha(theme.palette.background.paper, 0.8),
color: theme.palette.text.primary,
margin: SPACING,
},
bp: {
display: 'flex',
alignItems: 'center',
marginLeft: 5,
},
toggleButton: {
height: 44,
border: 'none',
marginLeft: theme.spacing(4),
},
buttonSpacer: {
marginRight: theme.spacing(2),
},
}))

const HeaderButtons = observer(({ model }: { model: LGV }) => {
const { classes } = useStyles()
return (
<IconButton
onClick={model.activateTrackSelector}
className={classes.toggleButton}
title="Open track selector"
value="track_select"
>
<TrackSelectorIcon className={classes.buttonSpacer} />
</IconButton>
)
})

function PanControls({ model }: { model: LGV }) {
const { classes } = useStyles()
return (
<>
<Button
variant="outlined"
className={classes.panButton}
onClick={() => {
model.slide(-0.9)
}}
>
<ArrowBackIcon />
</Button>
<Button
variant="outlined"
className={classes.panButton}
onClick={() => {
model.slide(0.9)
}}
>
<ArrowForwardIcon />
</Button>
</>
)
}

const RegionWidth = observer(function ({ model }: { model: LGV }) {
const { classes } = useStyles()
const { coarseTotalBp } = model
return (
<Typography variant="body2" color="textSecondary" className={classes.bp}>
{getBpDisplayStr(coarseTotalBp)}
</Typography>
)
})

const Controls = ({ model }: { model: LGV }) => {
const Controls = function ({ model }: { model: LinearGenomeViewModel }) {
const { classes } = useStyles()
return (
<div className={classes.headerBar}>
<HeaderButtons model={model} />
<HeaderTrackSelectorButton model={model} />
<div className={classes.spacer} />
<FormGroup row className={classes.headerForm}>
<PanControls model={model} />
<HeaderPanControls model={model} />
<SearchBox model={model} />
</FormGroup>
<RegionWidth model={model} />
<ZoomControls model={model} />
<HeaderRegionWidth model={model} />
<HeaderZoomControls model={model} />
<div className={classes.spacer} />
</div>
)
}

const LinearGenomeViewHeader = observer(function ({ model }: { model: LGV }) {
const LinearGenomeViewHeader = observer(function ({
model,
}: {
model: LinearGenomeViewModel
}) {
const { hideHeader, hideHeaderOverview } = model
return !hideHeader ? (
hideHeaderOverview ? (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import ArrowBackIcon from '@mui/icons-material/ArrowBack'
import ArrowForwardIcon from '@mui/icons-material/ArrowForward'
import { Button, alpha } from '@mui/material'
import { makeStyles } from 'tss-react/mui'

import { SPACING } from '../consts'

import type { LinearGenomeViewModel } from '..'

type LGV = LinearGenomeViewModel
const useStyles = makeStyles()(theme => ({
panButton: {
background: alpha(theme.palette.background.paper, 0.8),
color: theme.palette.text.primary,
margin: SPACING,
},

buttonSpacer: {
marginRight: theme.spacing(2),
},
}))

export default function HeaderPanControls({ model }: { model: LGV }) {
const { classes } = useStyles()
return (
<>
<Button
variant="outlined"
className={classes.panButton}
onClick={() => {
model.slide(-0.9)
}}
>
<ArrowBackIcon />
</Button>
<Button
variant="outlined"
className={classes.panButton}
onClick={() => {
model.slide(0.9)
}}
>
<ArrowForwardIcon />
</Button>
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { getBpDisplayStr } from '@jbrowse/core/util'
import { Typography } from '@mui/material'
import { observer } from 'mobx-react'
import { makeStyles } from 'tss-react/mui'

import type { LinearGenomeViewModel } from '..'

const useStyles = makeStyles()({
bp: {
display: 'flex',
alignItems: 'center',
marginLeft: 5,
cursor: 'pointer',
},
})

const HeaderRegionWidth = observer(function ({
model,
}: {
model: LinearGenomeViewModel
}) {
const { classes } = useStyles()
const { coarseTotalBp } = model
return (
<Typography variant="body2" color="textSecondary" className={classes.bp}>
{getBpDisplayStr(coarseTotalBp)}
</Typography>
)
})

export default HeaderRegionWidth
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { TrackSelector as TrackSelectorIcon } from '@jbrowse/core/ui/Icons'
import { IconButton } from '@mui/material'
import { observer } from 'mobx-react'
import { makeStyles } from 'tss-react/mui'

import type { LinearGenomeViewModel } from '..'

const useStyles = makeStyles()(theme => ({
toggleButton: {
height: 44,
border: 'none',
marginLeft: theme.spacing(4),
},
}))

const HeaderTrackSelectorButton = observer(function ({
model,
}: {
model: LinearGenomeViewModel
}) {
const { classes } = useStyles()
return (
<IconButton
onClick={model.activateTrackSelector}
className={classes.toggleButton}
title="Open track selector"
value="track_select"
>
<TrackSelectorIcon />
</IconButton>
)
})

export default HeaderTrackSelectorButton
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react'
import { lazy, useEffect, useState } from 'react'

import CascadingMenuButton from '@jbrowse/core/ui/CascadingMenuButton'
import { getSession } from '@jbrowse/core/util'
import MoreVert from '@mui/icons-material/MoreVert'
import ZoomIn from '@mui/icons-material/ZoomIn'
import ZoomOut from '@mui/icons-material/ZoomOut'
Expand All @@ -10,6 +11,9 @@ import { makeStyles } from 'tss-react/mui'

import type { LinearGenomeViewModel } from '..'

// lazies
const RegionWidthEditorDialog = lazy(() => import('./RegionWidthEditorDialog'))

const useStyles = makeStyles()(theme => ({
container: {
display: 'flex',
Expand All @@ -22,7 +26,7 @@ const useStyles = makeStyles()(theme => ({
},
}))

const ZoomControls = observer(function ({
const HeaderZoomControls = observer(function ({
model,
}: {
model: LinearGenomeViewModel
Expand Down Expand Up @@ -90,6 +94,18 @@ const ZoomControls = observer(function ({
model.zoom(model.bpPerPx * r)
},
})),
{
label: 'Custom zoom',
onClick: () => {
getSession(model).queueDialog(handleClose => [
RegionWidthEditorDialog,
{
model,
handleClose,
},
])
},
},
]}
>
<MoreVert />
Expand All @@ -98,4 +114,4 @@ const ZoomControls = observer(function ({
)
})

export default ZoomControls
export default HeaderZoomControls
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { useEffect, useState } from 'react'

import { Dialog } from '@jbrowse/core/ui'
import { toLocale } from '@jbrowse/core/util'
import {
Button,
DialogActions,
DialogContent,
TextField,
Typography,
} from '@mui/material'
import { observer } from 'mobx-react'

import type { LinearGenomeViewModel } from '../model'

const toP = (s = 0) => +(+s).toFixed(1)

const RegionWidthEditorDialog = observer(function ({
model,
handleClose,
}: {
model: LinearGenomeViewModel
handleClose: () => void
}) {
const { bpPerPx, width } = model
const [val, setVal] = useState(toLocale(toP(bpPerPx * width)))
useEffect(() => {
setVal(toLocale(bpPerPx * width))
}, [bpPerPx, width])
const val2 = val.replace(/,/g, '')
return (
<Dialog title="Edit zoom level" open onClose={handleClose}>
<DialogContent
style={{
display: 'flex',
flexDirection: 'column',
gap: 30,
}}
>
<Typography>
Enter a specific number of base pairs to change the viewport to show.
This is approximate and does not account for padding between regions
or off-screen scrolling
</Typography>
<TextField
helperText="current zoom level (in bp)"
value={val}
onChange={event => {
setVal(event.target.value)
}}
/>
</DialogContent>
<DialogActions>
<Button
variant="contained"
color="secondary"
onClick={() => {
handleClose()
}}
>
Cancel
</Button>
<Button
variant="contained"
color="primary"
onClick={() => {
model.zoomTo(+val2 / model.width)
handleClose()
}}
>
Submit
</Button>
</DialogActions>
</Dialog>
)
})

export default RegionWidthEditorDialog
Loading

0 comments on commit 06c5f9f

Please sign in to comment.