Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds URL query param for highlight on LGV #4234

Merged
merged 11 commits into from
Feb 27, 2024
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
17 changes: 16 additions & 1 deletion plugins/linear-genome-view/src/LaunchLinearGenomeView/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import PluginManager from '@jbrowse/core/PluginManager'
import { AbstractSessionModel, when } from '@jbrowse/core/util'
import { AbstractSessionModel, when, parseLocString } from '@jbrowse/core/util'
// locals
import { LinearGenomeViewModel } from '../LinearGenomeView'
import { handleSelectedRegion } from '../searchUtils'
Expand All @@ -17,16 +17,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 +64,16 @@ export default (pluginManager: PluginManager) => {
if (nav !== undefined) {
view.setHideHeader(!nav)
}
if (highlight !== undefined) {
const location = parseLocString(highlight, refName =>
isValidRefName(refName, assembly),
)
if (location?.start && location?.end) {
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,174 @@
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, getSession } from '@jbrowse/core/util'
import { Menu } from '@jbrowse/core/ui'
import { Base1DViewModel } from '@jbrowse/core/util/Base1DViewModel'
import { IconButton, Tooltip } from '@mui/material'

// icons
import LinkIcon from '@mui/icons-material/Link'

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

type LGV = LinearGenomeViewModel

const COLOR = 'rgb(218, 165, 32)'
carolinebridge marked this conversation as resolved.
Show resolved Hide resolved

const useStyles = makeStyles()({
highlight: {
height: '100%',
position: 'absolute',
textAlign: 'center',
overflow: 'hidden',
display: 'flex',
carolinebridge marked this conversation as resolved.
Show resolved Hide resolved
alignItems: 'start',
},
})

interface ParsedLocStringA {
assemblyName: string
refName: string
start: number
end: number
reversed: boolean
}

const Highlight = observer(function Highlight({ model }: { model: LGV }) {
const { classes } = useStyles()
const [open, setOpen] = useState(false)
const anchorEl = useRef(null)

const session = getSession(model)

const menuItems = [
{
label: 'Dismiss highlight',
onClick: () => model.setHighlight({} as ParsedLocString),
},
]
carolinebridge marked this conversation as resolved.
Show resolved Hide resolved

function handleClose() {
setOpen(false)
}

if (!model.highlight) {
return
}

// coords
const mapCoords = (r: ParsedLocStringA) => {
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 ParsedLocStringA)

return (
<>
{h ? (
<div
className={classes.highlight}
style={{
left: h.left,
width: h.width,
background: `${colord(COLOR).alpha(0.35).toRgbString()}`,
borderLeft: `solid ${COLOR}`,
borderRight: `solid ${COLOR}`,
}}
>
<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}
// anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
onMenuItemClick={(_event, callback) => {
callback(session)
handleClose()
}}
open={open}
onClose={handleClose}
menuItems={menuItems}
/>
</div>
) : null}
</>
)
})

export const OverviewHighlight = observer(function OverviewHighlight({
carolinebridge marked this conversation as resolved.
Show resolved Hide resolved
model,
overview,
}: {
model: LGV
overview: Base1DViewModel
}) {
const { classes } = useStyles()

const { cytobandOffset } = model

// coords
const mapCoords = (r: ParsedLocStringA) => {
const s =
overview.bpToPx({
...r,
coord: r.reversed ? r.end : r.start,
}) || 0

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

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

const h = mapCoords(model.highlight as ParsedLocStringA)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i believe this cast is "not valid", as model.highlight.start and model.highlight.end are potentially undefined by definition of ParsedLocString(?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most recent commit: is making use of Required https://www.typescriptlang.org/docs/handbook/utility-types.html#requiredtype satisfactory?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that is an interesting new feature of typescript, but I might opt for creating a new type (just the simple {assemblyName:string,refName:string,start:number,end:number} instead perhaps, and try to avoid using "as" cast entirely (casts are generally best if avoided as it cancels out any check that typescript would do for you)


return (
<>
{h ? (
<div
className={classes.highlight}
style={{
width: h.width,
left: h.left,
background: `${colord(COLOR).alpha(0.35).toRgbString()}`,
borderLeft: `solid ${COLOR}`,
borderRight: `solid ${COLOR}`,
}}
/>
) : null}
</>
)
})

export default Highlight
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 './Highlight'

const wholeSeqSpacer = 2

Expand Down Expand Up @@ -263,6 +264,7 @@ const Scalebar = observer(function ({
/>
)
})}
<OverviewHighlight model={model} overview={overview} />
</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
15 changes: 15 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,15 @@ 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.optional(
types.frozen<ParsedLocString>(),
{} as ParsedLocString,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this cast may not be valid, as refName is required on ParsedLocString. potentially, making it something like {assemblyName:string,refName:string,start:number,end:number}|undefined might be better than parsedlocstring, because then you know you have all required fields, or else it is just undefined

),
}),
)
.volatile(() => ({
Expand Down Expand Up @@ -581,6 +590,12 @@ export function stateModelFactory(pluginManager: PluginManager) {
setShowGridlines(b: boolean) {
self.showGridlines = b
},
/**
* #action
*/
setHighlight(highlight: ParsedLocString) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with above comment, can make it "|undefined" so that undefined clears it

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
Loading