Scalebar and track labels
A scalebar with gridlines and drag-to-zoom, and labels with resize bars.
Scalebar, gridlines and drag to zoom
View source — 143 lines
import { useRef, useState } from 'react'
import {
SessionPaletteProvider,
usePalette,
} from '@jbrowse/core/ui/PaletteContext'
import { usePointerDrag } from '@jbrowse/core/util/usePointerDrag'
import { DisplayUIProvider } from '@jbrowse/display-ui'
import { RegionSeams, Scalebar, TrackStack } from '@jbrowse/display-ui/embed'
import { useCreateViewState } from '@jbrowse/react-linear-genome-view2'
import { observer } from 'mobx-react'
import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
const Gridlines = observer(function Gridlines({
view,
}: {
view: LinearGenomeViewModel
}) {
const palette = usePalette()
const path = (major: boolean) =>
view.gridlineTicks
.filter(tick => tick.major === major)
.map(tick => `M${tick.x + 0.5} 0V100000`)
.join('')
return (
<svg
aria-hidden
style={{
position: 'absolute',
top: 0,
left: 0,
height: '100%',
width: view.staticBlocks.totalWidthPx,
transform: `translateX(${view.staticBlocksTranslateX}px)`,
pointerEvents: 'none',
}}
>
<path d={path(false)} style={{ stroke: palette.gridlineMinor }} />
<path d={path(true)} style={{ stroke: palette.gridlineMajor }} />
</svg>
)
})
function useDragToZoom(view: LinearGenomeViewModel) {
const [range, setRange] = useState<{ left: number; right: number }>()
const start = useRef({ anchor: 0, origin: 0 })
const span = (clientX: number) => {
const { anchor, origin } = start.current
const x = Math.min(Math.max(clientX - origin, 0), view.width)
return { left: Math.min(anchor, x), right: Math.max(anchor, x) }
}
const props = usePointerDrag({
onDragStart(event) {
const origin = event.currentTarget.getBoundingClientRect().left
start.current = { anchor: event.clientX - origin, origin }
},
onDrag(event) {
setRange(span(event.clientX))
},
onDragEnd(event) {
const { left, right } = span(event.clientX)
setRange(undefined)
if (right - left >= 4) {
view.moveTo(view.pxToBp(left), view.pxToBp(right))
}
},
})
return { range, props }
}
const Demo = observer(function Demo({ view }: { view: LinearGenomeViewModel }) {
const palette = usePalette()
const { range, props } = useDragToZoom(view)
return (
<TrackStack view={view}>
<Gridlines view={view} />
<Scalebar
view={view}
data-testid="scalebar"
style={{ cursor: 'crosshair', touchAction: 'none' }}
{...props}
/>
<RegionSeams view={view} />
{range ? (
<div
data-testid="rubberband"
style={{
position: 'absolute',
top: 0,
bottom: 0,
left: range.left,
width: range.right - range.left,
zIndex: 4,
pointerEvents: 'none',
background: `color-mix(in srgb, ${palette.primary.main} 20%, transparent)`,
}}
/>
) : null}
</TrackStack>
)
})
const ScalebarAndGridlines = observer(function ScalebarAndGridlines() {
const state = useCreateViewState({
assembly: {
name: 'hg38',
uri: 'https://jbrowse.org/genomes/GRCh38/fasta/hg38.prefix.fa.gz',
refNameAliases: {
uri: 'https://jbrowse.org/genomes/GRCh38/hg38_aliases.txt',
},
geneticCodes: { chrM: 2 },
},
tracks: [
{
trackId: 'hg38_phylop',
name: 'phyloP 100-way conservation',
uri: 'https://jbrowse.org/demos/phylop/hg38.phyloP100way.brca1.bw',
displayDefaults: { height: 100, color: '#3a7ca5' },
},
{
trackId: 'hg38_genes',
name: 'RefSeq curated genes',
uri: 'https://jbrowse.org/ucsc/hg38/ncbiRefSeqCurated.gff.gz',
index: 'https://jbrowse.org/ucsc/hg38/ncbiRefSeqCurated.gff.gz.csi',
displayDefaults: { height: 120 },
},
],
init: {
loc: 'chr17:43,044,295..43,060,000 chr17:43,100,000..43,125,364',
tracks: ['hg38_phylop', 'hg38_genes'],
},
})
return state ? (
<SessionPaletteProvider session={state.session}>
<DisplayUIProvider>
<Demo view={state.session.view} />
</DisplayUIProvider>
</SessionPaletteProvider>
) : null
})
export default ScalebarAndGridlinesTrack labels and resize bars
View source — 157 lines
import { SessionPaletteProvider } from '@jbrowse/core/ui/PaletteContext'
import { useWidthSetter } from '@jbrowse/core/util/hooks'
import { usePanZoom } from '@jbrowse/core/util/usePanZoom'
import { useResizeDrag } from '@jbrowse/core/util/useResizeDrag'
import { DisplayUIProvider } from '@jbrowse/display-ui'
import { Track, ViewStatus } from '@jbrowse/display-ui/embed'
import { useCreateViewState } from '@jbrowse/react-linear-genome-view2'
import { observer } from 'mobx-react'
import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
const labels = {
hg38_phylop: 'Conservation',
hg38_genes: 'Genes',
na12878_exome: 'Reads',
}
const ids = Object.keys(labels) as (keyof typeof labels)[]
const BAR = 4
const ResizeBar = observer(function ResizeBar({
view,
trackId,
}: {
view: LinearGenomeViewModel
trackId: string
}) {
const display = view.getTrack(trackId)?.activeDisplay
const props = useResizeDrag({
onDragStart: () => {
display?.setResizing(true)
},
onDrag: distance => {
display?.resizeHeight(distance)
},
onDragEnd: () => {
display?.setResizing(false)
},
})
return (
<div
{...props}
aria-label={`Resize ${trackId}`}
style={{
height: BAR,
cursor: 'row-resize',
touchAction: 'none',
background: 'color-mix(in srgb, currentColor 20%, transparent)',
}}
/>
)
})
const Labels = observer(function Labels({
view,
}: {
view: LinearGenomeViewModel
}) {
return (
<div style={{ width: 90, flex: 'none', fontSize: '0.75rem' }}>
{ids.map(id => {
const display = view.getTrack(id)?.activeDisplay
return display ? (
<div
key={id}
style={{
height: display.height + BAR,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}
>
{labels[id]}
</div>
) : null
})}
</div>
)
})
const Tracks = observer(function Tracks({
view,
}: {
view: LinearGenomeViewModel
}) {
const ref = useWidthSetter(view)
const { containerProps } = usePanZoom(ref, view)
return (
<div
ref={ref}
{...containerProps}
style={{ position: 'relative', overflow: 'hidden', flex: 1, minWidth: 0 }}
>
{view.status.type === 'ready' ? (
ids.map(id => (
<div key={id}>
<Track view={view} trackId={id} />
<ResizeBar view={view} trackId={id} />
</div>
))
) : (
<ViewStatus view={view} />
)}
</div>
)
})
const TrackLabels = observer(function TrackLabels() {
const state = useCreateViewState({
assembly: {
name: 'hg38',
uri: 'https://jbrowse.org/genomes/GRCh38/fasta/hg38.prefix.fa.gz',
refNameAliases: {
uri: 'https://jbrowse.org/genomes/GRCh38/hg38_aliases.txt',
},
geneticCodes: { chrM: 2 },
},
tracks: [
{
trackId: 'hg38_phylop',
name: 'phyloP 100-way conservation',
uri: 'https://jbrowse.org/demos/phylop/hg38.phyloP100way.brca1.bw',
displayDefaults: { height: 100, color: '#3a7ca5' },
},
{
trackId: 'hg38_genes',
name: 'RefSeq curated genes',
uri: 'https://jbrowse.org/ucsc/hg38/ncbiRefSeqCurated.gff.gz',
index: 'https://jbrowse.org/ucsc/hg38/ncbiRefSeqCurated.gff.gz.csi',
displayDefaults: { height: 120 },
},
{
trackId: 'na12878_exome',
name: 'NA12878 exome reads',
uri: 'https://s3.amazonaws.com/jbrowse.org/genomes/GRCh38/alignments/NA12878/NA12878.alt_bwamem_GRCh38DH.20150826.CEU.exome.cram',
displayDefaults: { height: 150 },
},
],
init: {
loc: 'chr17:43,044,295..43,125,364',
tracks: ids,
},
})
return state ? (
<SessionPaletteProvider session={state.session}>
<DisplayUIProvider>
<div style={{ display: 'flex' }}>
<Labels view={state.session.view} />
<Tracks view={state.session.view} />
</div>
</DisplayUIProvider>
</SessionPaletteProvider>
) : null
})
export default TrackLabels