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 SearchByNameYour 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