Skip to content

Commit

Permalink
Adds URL query param for highlight on LGV (#4234)
Browse files Browse the repository at this point in the history

---------

Co-authored-by: Colin <colin.diesh@gmail.com>
  • Loading branch information
carolinebridge and cmdcolin authored Feb 27, 2024
1 parent 9f43937 commit 8f8da20
Show file tree
Hide file tree
Showing 9 changed files with 254 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import { observer } from 'mobx-react'
import { makeStyles } from 'tss-react/mui'
import { SessionWithWidgets, getSession, notEmpty } from '@jbrowse/core/util'
import { colord } from '@jbrowse/core/util/colord'
import { Tooltip } from '@mui/material'

// icons
import BookmarkIcon from '@mui/icons-material/Bookmark'

// locals
import { Tooltip } from '@mui/material'
import { GridBookmarkModel } from '../../model'
import { IExtendedLGV } from '../../model'

Expand Down
21 changes: 20 additions & 1 deletion plugins/linear-genome-view/src/LaunchLinearGenomeView/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import PluginManager from '@jbrowse/core/PluginManager'
import { AbstractSessionModel, when } from '@jbrowse/core/util'
import {
AbstractSessionModel,
when,
parseLocString,
ParsedLocString,
} from '@jbrowse/core/util'
// locals
import { LinearGenomeViewModel } from '../LinearGenomeView'
import { handleSelectedRegion } from '../searchUtils'
Expand All @@ -17,16 +22,21 @@ export default (pluginManager: PluginManager) => {
tracks = [],
tracklist,
nav,
highlight,
}: {
session: AbstractSessionModel
assembly?: string
loc: string
tracks?: string[]
tracklist?: boolean
nav?: boolean
highlight?: string
}) => {
try {
const { assemblyManager } = session

const { isValidRefName } = assemblyManager

const view = session.addView('LinearGenomeView', {}) as LGV

await when(() => !!view.volatileWidth)
Expand Down Expand Up @@ -59,6 +69,15 @@ export default (pluginManager: PluginManager) => {
if (nav !== undefined) {
view.setHideHeader(!nav)
}
if (highlight !== undefined) {
const location = parseLocString(highlight, refName =>
isValidRefName(refName, assembly),
) as Required<ParsedLocString>
if (location?.start !== undefined && location?.end !== undefined) {
location.assemblyName = assembly
view.setHighlight(location)
}
}
} catch (e) {
session.notify(`${e}`, 'error')
throw e
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import React, { useRef, useState } from 'react'
import { observer } from 'mobx-react'
import { makeStyles } from 'tss-react/mui'
import { colord } from '@jbrowse/core/util/colord'
import {
ParsedLocString,
Region,
SessionWithWidgets,
getSession,
} from '@jbrowse/core/util'
import { Menu } from '@jbrowse/core/ui'
import { IconButton, Tooltip, useTheme } from '@mui/material'

// icons
import LinkIcon from '@mui/icons-material/Link'
import CloseIcon from '@mui/icons-material/Close'
import BookmarkIcon from '@mui/icons-material/Bookmark'

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

type LGV = LinearGenomeViewModel

const useStyles = makeStyles()(theme => ({
highlight: {
height: '100%',
position: 'absolute',
background: `${colord(theme.palette.quaternary?.main ?? 'goldenrod')
.alpha(0.35)
.toRgbString()}`,
borderLeft: `1px solid ${theme.palette.quaternary?.main ?? 'goldenrod'}`,
borderRight: `1px solid ${theme.palette.quaternary?.main ?? 'goldenrod'}`,
},
}))

const Highlight = observer(function Highlight({ model }: { model: LGV }) {
const { classes } = useStyles()
const [open, setOpen] = useState(false)
const anchorEl = useRef(null)
const color = useTheme().palette.quaternary?.main ?? 'goldenrod'

const session = getSession(model) as SessionWithWidgets

const dismissHighlight = () => {
model.setHighlight(undefined)
}

const menuItems = [
{
label: 'Dismiss highlight',
icon: CloseIcon,
onClick: () => dismissHighlight(),
},
{
label: 'Bookmark highlighted region',
icon: BookmarkIcon,
onClick: () => {
let bookmarkWidget = session.widgets.get('GridBookmark')
if (!bookmarkWidget) {
bookmarkWidget = session.addWidget(
'GridBookmarkWidget',
'GridBookmark',
)
}
// @ts-ignore
bookmarkWidget.addBookmark(model.highlight as Region)
dismissHighlight()
},
},
]

function handleClose() {
setOpen(false)
}

if (!model.highlight) {
return
}

// coords
const mapCoords = (r: Required<ParsedLocString>) => {
const s = model.bpToPx({
refName: r.refName,
coord: r.start,
})
const e = model.bpToPx({
refName: r.refName,
coord: r.end,
})
return s && e
? {
width: Math.max(Math.abs(e.offsetPx - s.offsetPx), 3),
left: Math.min(s.offsetPx, e.offsetPx) - model.offsetPx,
}
: undefined
}

const h = mapCoords(model.highlight as Required<ParsedLocString>)

return (
<>
{h ? (
<div
className={classes.highlight}
style={{
left: h.left,
width: h.width,
}}
>
<Tooltip title={'Highlighted from URL parameter'} arrow>
<IconButton ref={anchorEl} onClick={() => setOpen(true)}>
<LinkIcon
fontSize="small"
sx={{
color: `${colord(color).darken(0.2).toRgbString()}`,
}}
/>
</IconButton>
</Tooltip>
<Menu
anchorEl={anchorEl.current}
onMenuItemClick={(_event, callback) => {
callback(session)
handleClose()
}}
open={open}
onClose={handleClose}
menuItems={menuItems}
/>
</div>
) : null}
</>
)
})

export default Highlight
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React from 'react'
import { observer } from 'mobx-react'
import { makeStyles } from 'tss-react/mui'
import { colord } from '@jbrowse/core/util/colord'
import { ParsedLocString } from '@jbrowse/core/util'
import { Base1DViewModel } from '@jbrowse/core/util/Base1DViewModel'

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

type LGV = LinearGenomeViewModel

const useStyles = makeStyles()(theme => ({
highlight: {
height: '100%',
position: 'absolute',
background: `${colord(theme.palette.quaternary?.main ?? 'goldenrod')
.alpha(0.35)
.toRgbString()}`,
borderLeft: `1px solid ${theme.palette.quaternary?.main ?? 'goldenrod'}`,
borderRight: `1px solid ${theme.palette.quaternary?.main ?? 'goldenrod'}`,
},
}))

const OverviewHighlight = observer(function OverviewHighlight({
model,
overview,
}: {
model: LGV
overview: Base1DViewModel
}) {
const { classes } = useStyles()
const { cytobandOffset } = model

// coords
const mapCoords = (r: Required<ParsedLocString>) => {
const s = overview.bpToPx({
...r,
coord: r.reversed ? r.end : r.start,
})

const e = overview.bpToPx({
...r,
coord: r.reversed ? r.start : r.end,
})

return s !== undefined && e != undefined
? {
width: Math.abs(e - s),
left: s + cytobandOffset,
}
: undefined
}

const h = mapCoords(model.highlight as Required<ParsedLocString>)

return (
<>
{h ? (
<div
className={classes.highlight}
style={{
width: h.width,
left: h.left,
}}
/>
) : null}
</>
)
})

export default OverviewHighlight
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { getCytobands } from './util'
import OverviewRubberband from './OverviewRubberband'
import Cytobands from './Cytobands'
import OverviewScalebarPolygon from './OverviewScalebarPolygon'
import OverviewHighlight from './OverviewHighlight'

const wholeSeqSpacer = 2

Expand Down Expand Up @@ -263,6 +264,9 @@ const Scalebar = observer(function ({
/>
)
})}
{model.highlight ? (
<OverviewHighlight model={model} overview={overview} />
) : null}
</div>
)
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import Gridlines from './Gridlines'
import CenterLine from './CenterLine'
import VerticalGuide from './VerticalGuide'
import RubberbandSpan from './RubberbandSpan'
import Highlight from './Highlight'

const useStyles = makeStyles()({
tracksContainer: {
Expand Down Expand Up @@ -107,6 +108,7 @@ const TracksContainer = observer(function TracksContainer({
/>
}
/>
<Highlight model={model} />
{additional}
{children}
</div>
Expand Down
12 changes: 12 additions & 0 deletions plugins/linear-genome-view/src/LinearGenomeView/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,12 @@ export function stateModelFactory(pluginManager: PluginManager) {
* show the "gridlines" in the track area
*/
showGridlines: true,

/**
* #property
* highlights on the LGV from the URL parameters
*/
highlight: types.maybe(types.frozen<Required<ParsedLocString>>()),
}),
)
.volatile(() => ({
Expand Down Expand Up @@ -581,6 +587,12 @@ export function stateModelFactory(pluginManager: PluginManager) {
setShowGridlines(b: boolean) {
self.showGridlines = b
},
/**
* #action
*/
setHighlight(highlight: Required<ParsedLocString> | undefined) {
self.highlight = highlight
},
/**
* #action
*/
Expand Down
3 changes: 3 additions & 0 deletions products/jbrowse-web/src/SessionLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const SessionLoader = types
assembly: types.maybe(types.string),
tracks: types.maybe(types.string),
tracklist: types.maybe(types.boolean),
highlight: types.maybe(types.string),
nav: types.maybe(types.boolean),
initialTimestamp: types.number,
})
Expand Down Expand Up @@ -285,6 +286,7 @@ const SessionLoader = types
assembly,
tracklist,
nav,
highlight,
sessionTracksParsed: sessionTracks,
} = self
if (loc) {
Expand All @@ -299,6 +301,7 @@ const SessionLoader = types
assembly,
tracklist,
nav,
highlight,
},
],
}
Expand Down
4 changes: 4 additions & 0 deletions products/jbrowse-web/src/components/Loader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export function Loader({
const [sessionTracks, setSessionTracks] = useQueryParam('sessionTracks', Str)
const [assembly, setAssembly] = useQueryParam('assembly', Str)
const [tracks, setTracks] = useQueryParam('tracks', Str)
const [highlight, setHighlight] = useQueryParam('highlight', Str)
const [nav, setNav] = useQueryParam('nav', Str)
const [tracklist, setTrackList] = useQueryParam('tracklist', Str)

Expand All @@ -61,6 +62,7 @@ export function Loader({
tracks: normalize(tracks),
sessionTracks: normalize(sessionTracks),
tracklist: JSON.parse(normalize(tracklist) || 'false'),
highlight: normalize(highlight),
nav: JSON.parse(normalize(nav) || 'true'),
initialTimestamp,
})
Expand All @@ -73,6 +75,7 @@ export function Loader({
setSessionTracks(undefined, 'replaceIn')
setTrackList(undefined, 'replaceIn')
setNav(undefined, 'replaceIn')
setHighlight(undefined, 'replaceIn')
}, [
setAssembly,
setLoc,
Expand All @@ -81,6 +84,7 @@ export function Loader({
setTracks,
setPassword,
setSessionTracks,
setHighlight,
])

return <Renderer loader={loader} />
Expand Down

0 comments on commit 8f8da20

Please sign in to comment.