JBrowse 2 · Build Your Own examples

Searching by name

Gene names in a location box, and your own list of hits.

A name instead of a locstring

The index is three files from jbrowse text-index. Each hit records the trackId the index was built against, so the genes track here is genes: a mismatched id still navigates, then fails to show the track. BRC matches five genes and none exactly, so JBrowse queues a dialog this page never draws.

View source — 121 lines
import { SessionPaletteProvider } from '@jbrowse/core/ui/PaletteContext'
import { DisplayUIProvider } from '@jbrowse/display-ui'
import { TrackStack, useLocationBox } from '@jbrowse/display-ui/embed'
import { SearchResultsNotFoundError } from '@jbrowse/plugin-linear-genome-view'
import { useCreateViewState } from '@jbrowse/react-linear-genome-view2'
import { observer } from 'mobx-react'

import type { ViewModel } from '@jbrowse/react-linear-genome-view2'

const queries = ['chr13', 'gene15876', 'TP53', 'BRC', 'zzzznotagene']

const SearchBox = observer(function SearchBox({
  session,
}: {
  session: ViewModel['session']
}) {
  const box = useLocationBox(session.view)
  const queued = session.queueOfDialogs.length
  return (
    <div style={{ display: 'grid', gap: 6, paddingBottom: 8 }}>
      <form
        onSubmit={event => {
          event.preventDefault()
          box.go()
        }}
      >
        <input
          aria-label="Search by name or location"
          value={box.value}
          size={28}
          onChange={event => {
            box.edit(event.target.value)
          }}
        />
        <button type="submit">{box.pending ? 'Searching…' : 'Go'}</button>
      </form>
      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
        {queries.map(query => (
          <button
            key={query}
            type="button"
            onClick={() => {
              box.go(query)
            }}
          >
            {query}
          </button>
        ))}
      </div>
      {box.error instanceof SearchResultsNotFoundError ? (
        <span role="status">{box.error.message}</span>
      ) : box.error ? (
        <span role="alert">
          {box.error instanceof Error ? box.error.message : String(box.error)}
        </span>
      ) : null}
      {queued ? (
        <div role="status" data-testid="queued-dialog-notice">
          {queued} dialog{queued > 1 ? 's' : ''} queued, and this page renders
          none.{' '}
          <button
            type="button"
            onClick={() => {
              session.removeActiveDialog()
            }}
          >
            Dismiss
          </button>
        </div>
      ) : null}
    </div>
  )
})

const SearchByName = observer(function SearchByName() {
  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: 'genes',
        name: 'NCBI RefSeq genes',
        uri: 'https://s3.amazonaws.com/jbrowse.org/genomes/GRCh38/ncbi_refseq/GCA_000001405.15_GRCh38_full_analysis_set.refseq_annotation.sorted.gff.gz',
        displayDefaults: { height: 140 },
      },
    ],
    aggregateTextSearchAdapters: [
      {
        type: 'TrixTextSearchAdapter',
        textSearchAdapterId: 'hg38-index',
        assemblyNames: ['hg38'],
        ixFilePath: {
          uri: 'https://jbrowse.org/genomes/GRCh38/ncbi_refseq/trix/GCA_000001405.15_GRCh38_full_analysis_set.refseq_annotation.sorted.gff.gz.ix',
        },
        ixxFilePath: {
          uri: 'https://jbrowse.org/genomes/GRCh38/ncbi_refseq/trix/GCA_000001405.15_GRCh38_full_analysis_set.refseq_annotation.sorted.gff.gz.ixx',
        },
      },
    ],
    init: {
      loc: 'chr17:43,044,295..43,125,364',
      tracks: ['genes'],
    },
  })
  return state ? (
    <SessionPaletteProvider session={state.session}>
      <DisplayUIProvider>
        <SearchBox session={state.session} />
        <TrackStack view={state.session.view} />
      </DisplayUIProvider>
    </SessionPaletteProvider>
  ) : null
})

export default SearchByName

Your own list of hits

View source — 147 lines
import { useState } from 'react'

import { SessionPaletteProvider } from '@jbrowse/core/ui/PaletteContext'
import { useDebounce } from '@jbrowse/core/util/hooks'
import { useFetch } from '@jbrowse/core/util/useFetch'
import { DisplayUIProvider } from '@jbrowse/display-ui'
import { TrackStack } from '@jbrowse/display-ui/embed'
import { fetchResults } from '@jbrowse/plugin-linear-genome-view'
import { useCreateViewState } from '@jbrowse/react-linear-genome-view2'
import { observer } from 'mobx-react'

import type { ViewModel } from '@jbrowse/react-linear-genome-view2'

function SearchPanel({ session }: { session: ViewModel['session'] }) {
  const [query, setQuery] = useState('BRCA1')
  const debounced = useDebounce(query.trim(), 300)
  const { data, error, isLoading } = useFetch(
    debounced || null,
    async (queryString: string, signal: AbortSignal) =>
      fetchResults({
        queryString,
        signal,
        assemblyName: 'hg38',
        textSearchManager: session.textSearchManager,
        assembly: await session.assemblyManager.waitForAssembly('hg38'),
      }),
  )
  return (
    <div style={{ display: 'grid', gap: 4, width: 380, maxWidth: '100%' }}>
      <input
        aria-label="Search features"
        value={query}
        placeholder="BRCA1, TP53, chr17…"
        onChange={event => {
          setQuery(event.target.value)
        }}
      />
      {error ? (
        <span role="alert">
          {error instanceof Error ? error.message : String(error)}
        </span>
      ) : null}
      {data?.length ? (
        <ul
          data-testid="search-results"
          style={{
            listStyle: 'none',
            margin: 0,
            padding: 0,
            maxHeight: 132,
            overflowY: 'auto',
            fontSize: '0.8rem',
          }}
        >
          {data.map(result => {
            const location = result.getLocation()
            const trackId = result.getTrackId()
            return (
              <li key={result.getId()}>
                <button
                  type="button"
                  disabled={!location}
                  style={{ width: '100%', textAlign: 'left' }}
                  onClick={() => {
                    const { view } = session
                    if (location) {
                      setQuery('')
                      view
                        .navToLocString(location, 'hg38', 0.2)
                        .then(() =>
                          trackId ? view.launchTrack(trackId) : undefined,
                        )
                        .catch((e: unknown) => {
                          console.error(e)
                        })
                    }
                  }}
                >
                  <strong>{result.getDisplayString()}</strong> {location}{' '}
                  {trackId}
                </button>
              </li>
            )
          })}
        </ul>
      ) : (
        <span style={{ fontSize: '0.8rem', opacity: 0.6 }}>
          {!debounced
            ? 'Type a feature or contig name'
            : isLoading || debounced !== query.trim()
              ? 'Searching…'
              : `Nothing matches “${debounced}”`}
        </span>
      )}
    </div>
  )
}

const SearchResultsDropdown = observer(function SearchResultsDropdown() {
  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: 'genes',
        name: 'NCBI RefSeq genes',
        uri: 'https://s3.amazonaws.com/jbrowse.org/genomes/GRCh38/ncbi_refseq/GCA_000001405.15_GRCh38_full_analysis_set.refseq_annotation.sorted.gff.gz',
        displayDefaults: { height: 140 },
      },
    ],
    aggregateTextSearchAdapters: [
      {
        type: 'TrixTextSearchAdapter',
        textSearchAdapterId: 'hg38-index',
        assemblyNames: ['hg38'],
        ixFilePath: {
          uri: 'https://jbrowse.org/genomes/GRCh38/ncbi_refseq/trix/GCA_000001405.15_GRCh38_full_analysis_set.refseq_annotation.sorted.gff.gz.ix',
        },
        ixxFilePath: {
          uri: 'https://jbrowse.org/genomes/GRCh38/ncbi_refseq/trix/GCA_000001405.15_GRCh38_full_analysis_set.refseq_annotation.sorted.gff.gz.ixx',
        },
      },
    ],
    init: {
      loc: 'chr17:43,044,295..43,125,364',
      tracks: ['genes'],
    },
  })
  return state ? (
    <SessionPaletteProvider session={state.session}>
      <DisplayUIProvider>
        <div style={{ paddingBottom: 8 }}>
          <SearchPanel session={state.session} />
        </div>
        <TrackStack view={state.session.view} />
      </DisplayUIProvider>
    </SessionPaletteProvider>
  ) : null
})

export default SearchResultsDropdown