JBrowse 2 · Build Your Own examples

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 ScalebarAndGridlines

Track 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