Skip to content

Commit

Permalink
Clickable zoom
Browse files Browse the repository at this point in the history
  • Loading branch information
cmdcolin committed Jan 24, 2025
1 parent 535b258 commit 42d7e22
Show file tree
Hide file tree
Showing 8 changed files with 327 additions and 86 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,52 @@
import { lazy } from 'react'

import { getBpDisplayStr, getSession } 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 RegionWidthEditorDialog = lazy(() => import('./RegionWidthEditorDialog'))

const useStyles = makeStyles()({
bp: {
display: 'flex',
alignItems: 'center',
marginLeft: 5,
cursor: 'pointer',
minWidth: 50,
'&:hover': {
backgroundColor: 'rgba(0, 0, 0, 0.2)',
},
},
})

const HeaderRegionWidth = observer(function ({
model,
}: {
model: LinearGenomeViewModel
}) {
const { classes } = useStyles()
const { coarseTotalBp } = model
return (
<Typography
variant="body2"
color="textSecondary"
className={classes.bp}
onClick={() => {
getSession(model).queueDialog(handleClose => [
RegionWidthEditorDialog,
{
model,
handleClose,
},
])
}}
>
{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
@@ -0,0 +1,106 @@
import { useEffect, useState } from 'react'

import ZoomIn from '@mui/icons-material/ZoomIn'
import ZoomOut from '@mui/icons-material/ZoomOut'
import { IconButton, Slider, Tooltip } from '@mui/material'
import { observer } from 'mobx-react'
import { makeStyles } from 'tss-react/mui'

import type { LinearGenomeViewModel } from '..'

const useStyles = makeStyles()(theme => ({
container: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
},
slider: {
width: 100,
color: theme.palette.text.secondary,
},
}))

const HeaderZoomControls = observer(function ({
model,
}: {
model: LinearGenomeViewModel
}) {
const { classes } = useStyles()
const { maxBpPerPx, minBpPerPx, bpPerPx } = model
const [value, setValue] = useState(-Math.log2(bpPerPx) * 100)
useEffect(() => {
setValue(-Math.log2(bpPerPx) * 100)
}, [bpPerPx])
const zoomInDisabled = bpPerPx <= minBpPerPx + 0.0001
const zoomOutDisabled = bpPerPx >= maxBpPerPx - 0.0001
return (
<div className={classes.container}>
<Tooltip title="Zoom out 15x">
<span>
<IconButton
data-testid="zoom_out_more"
disabled={zoomOutDisabled}
onClick={() => {
model.zoom(bpPerPx * 15)
}}
>
<ZoomOut fontSize="large" />
</IconButton>
</span>
</Tooltip>
<Tooltip title="Zoom out 2x">
<span>
<IconButton
data-testid="zoom_out"
disabled={zoomOutDisabled}
onClick={() => {
model.zoom(bpPerPx * 2)
}}
>
<ZoomOut />
</IconButton>
</span>
</Tooltip>

<Slider
size="small"
className={classes.slider}
value={value}
min={-Math.log2(maxBpPerPx) * 100}
max={-Math.log2(minBpPerPx) * 100}
onChangeCommitted={() => model.zoomTo(2 ** (-value / 100))}
onChange={(_, val) => {
setValue(val as number)
}}
/>
<Tooltip title="Zoom in 2x">
<span>
<IconButton
data-testid="zoom_in"
disabled={zoomInDisabled}
onClick={() => {
model.zoom(model.bpPerPx / 2)
}}
>
<ZoomIn />
</IconButton>
</span>
</Tooltip>
<Tooltip title="Zoom in 15x">
<span>
<IconButton
data-testid="zoom_in_more"
disabled={zoomInDisabled}
onClick={() => {
model.zoom(model.bpPerPx / 15)
}}
>
<ZoomIn fontSize="large" />
</IconButton>
</span>
</Tooltip>
</div>
)
})

export default HeaderZoomControls
Loading

0 comments on commit 42d7e22

Please sign in to comment.