Skip to content

Commit

Permalink
feat: adding GenomeBrowserCard (#45)
Browse files Browse the repository at this point in the history
  • Loading branch information
holtgrewe authored Jan 30, 2024
1 parent 637db05 commit 3ecd6a1
Show file tree
Hide file tree
Showing 7 changed files with 333 additions and 0 deletions.
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"@protobuf-ts/runtime": "^2.9.3",
"@reactgular/chunks": "^1.0.1",
"@types/luxon": "^3.4.2",
"igv": "^2.15.11",
"luxon": "^3.4.4",
"title-case": "^4.3.1",
"vega": "^5.27.0",
Expand Down
42 changes: 42 additions & 0 deletions src/components/GenomeBrowserCard/GenomeBrowserCard.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { describe, expect, it } from 'vitest'

import { setupMountedComponents } from '../../lib/testUtils'
import GenomeBrowserCard from './GenomeBrowserCard.vue'

describe.concurrent('GenomeBrowserCard.vue', async () => {
it('renders the GenomeBrowserCard with the hg19 genome', async () => {
// arrange:
const { wrapper } = await setupMountedComponents(
{ component: GenomeBrowserCard },
{
props: {
genomeRelease: 'grch37',
locus: 'chr17:41246243-41246243'
}
}
)

// act: nothing, only test rendering

// assert:
expect(wrapper.exists()).toBe(true)
})

it('renders the GenomeBrowserCard with the hg38 genome', async () => {
// arrange:
const { wrapper } = await setupMountedComponents(
{ component: GenomeBrowserCard },
{
props: {
genomeRelease: 'grch38',
locus: 'chr17:41246243-41246243'
}
}
)

// act: nothing, only test rendering

// assert:
expect(wrapper.exists()).toBe(true)
})
})
107 changes: 107 additions & 0 deletions src/components/GenomeBrowserCard/GenomeBrowserCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<script setup lang="ts">
// @ts-nocheck
import igv from 'igv'
import { onMounted, ref, watch } from 'vue'
import type { GenomeBuild } from '../../lib/genomeBuilds'
import DocsLink from '../DocsLink/DocsLink.vue'
import { publicTracks } from './constants'
import { GenomeBrowser } from './types'
/** The component's props. */
const props = withDefaults(
defineProps<{
// Genome build
genomeBuild?: GenomeBuild
// Locus to go to, e.g., "chr1:1,900,000-2,000,000"
locus?: string
}>(),
{ genomeBuild: 'grch37', locus: '' }
)
/** The <div> to show the browser in. */
const genomeBrowserDivRef = ref(null)
/** Set on IGV browser creation. */
const igvBrowser = ref(null)
/**
* Translate genome build names from GRCh37/GRCh38 to hg19/hg38.
*
* @param value The genome build name.
* @returns The translated genome build name. (hg19/hg38)
*/
const translateGenome = (value: GenomeBuild) => {
if (value === 'grch37') {
return 'hg19'
} else if (value === 'grch38') {
return 'hg38'
} else {
return value
}
}
/**
* Add public tracks.
*
* @param browser The IGV browser.
*/
const addTracks = (browser: any) => {
for (const track of publicTracks) {
browser.loadTrack(track)
}
}
// Watch changes to the genome (requires full reload).
watch(
() => props.genomeBuild,
() => {
;(igvBrowser.value! as GenomeBrowser)
.loadGenome(translateGenome(props.genomeBuild))
.then((browser: GenomeBrowser) => {
browser.search(props.locus)
})
.then((browser: GenomeBrowser) => {
addTracks(browser)
})
}
)
// Watch changes to the locus (jumping is enough).
watch(
() => props.locus,
() => {
if (igvBrowser.value) {
;(igvBrowser.value! as GenomeBrowser).search(props.locus)
}
}
)
// Construct igv.js browser when mounted.
onMounted(() => {
igv
.createBrowser(genomeBrowserDivRef.value, {
genome: translateGenome(props.genomeBuild),
locus: props.locus
})
.then((browser: GenomeBrowser) => {
igvBrowser.value = browser
addTracks(browser)
if (props.locus) {
;(igvBrowser.value! as GenomeBrowser).search(props.locus)
}
})
})
</script>

<template>
<v-card class="mt-3">
<v-card-title class="pb-0 pr-2">
Genome Browser
<DocsLink anchor="genome-browser" />
</v-card-title>
<v-card-text>
<div ref="genomeBrowserDivRef" style="margin: 5px" />
</v-card-text>
</v-card>
</template>
@/lib/GenomeBrowser.c
73 changes: 73 additions & 0 deletions src/components/GenomeBrowserCard/constants.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { describe, expect, it } from 'vitest'

import { publicTracks } from './constants'

describe('publicTracks', () => {
it('should have at least one track', () => {
// arrange: nothing to do
// act: nothing to do

// assert:
expect(publicTracks.length).toBeGreaterThan(0)
})

it('should have a name for each track', () => {
// arrange: nothing to do
// act: nothing to do

// assert:
publicTracks.forEach((track) => {
expect(track.name).toBeDefined()
})
})

it('should have a sourceType for each track', () => {
// arrange: nothing to do
// act: nothing to do

// assert:
publicTracks.forEach((track) => {
expect(track.sourceType).toBeDefined()
})
})

it('should have a format for each track', () => {
// arrange: nothing to do
// act: nothing to do

// assert:
publicTracks.forEach((track) => {
expect(track.format).toBeDefined()
})
})

it('should have a visibilityWindow for each track', () => {
// arrange: nothing to do
// act: nothing to do

// assert:
publicTracks.forEach((track) => {
expect(track.visibilityWindow).toBeDefined()
})
})

it('should have a url for each track', () => {
// arrange: nothing to do
// act: nothing to do

// assert:
publicTracks.forEach((track) => {
expect(track.url).toBeDefined()
})
})

it('should have a color for each track', () => {
// arrange: nothing to do
// act: nothing to do

// assert:
publicTracks.forEach((track) => {
expect(track.color).toBeDefined()
})
})
})
102 changes: 102 additions & 0 deletions src/components/GenomeBrowserCard/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/** Default API base URL. */
const API_BASE_URL = `/internal/proxy/nginx/`

/** Visiblity window to use */
const visibilityWindow = 10000000

const hescTadTrack = {
name: 'hESC TADs',
sourceType: 'annotation',
format: 'bed',
visibilityWindow,
url: `${API_BASE_URL}grch37/hesc.bed`,
color: 'gray'
}

const curatedMmsTrack = {
name: 'Curated MMS',
sourceType: 'annotation',
format: 'bed',
visibilityWindow,
url: `${API_BASE_URL}grch37/patho-mms.bed`,
color: 'red'
}

const duplicationTrack = {
name: 'UCSC Segmental Duplications',
sourceType: 'annotation',
format: 'bed',
visibilityWindow,
url: `${API_BASE_URL}grch37/genomicSuperDups.bed.gz`,
indexURL: `${API_BASE_URL}grch37/genomicSuperDups.bed.gz.tbi`,
color: 'black'
}

const repeatsTrack = {
name: 'UCSC Repeat Masker',
sourceType: 'annotation',
format: 'bed',
visibilityWindow,
url: `${API_BASE_URL}grch37/rmsk.bed.gz`,
indexURL: `${API_BASE_URL}grch37/rmsk.bed.gz.tbi`,
color: 'black'
}

const altTrack = {
name: 'UCSC Alt Loci Track',
sourceType: 'annotation',
format: 'bed',
visibilityWindow,
url: `${API_BASE_URL}grch37/altSeqLiftOverPsl.bed.gz`,
indexURL: `${API_BASE_URL}grch37/altSeqLiftOverPsl.bed.gz.tbi`,
color: 'black'
}

const fixTrack = {
name: 'UCSC Fix Track',
sourceType: 'annotation',
format: 'bed',
visibilityWindow,
url: `${API_BASE_URL}grch37/fixSeqLiftOverPsl.bed.gz`,
indexURL: `${API_BASE_URL}grch37/fixSeqLiftOverPsl.bed.gz.tbi`,
color: 'black'
}

const bgDbTracks = [
{
title: 'gnomad-SV',
token: 'gnomad'
},
{
title: 'DGV SVs',
token: 'dgv'
},
{
title: 'DGV GS SVs',
token: 'dgv-gs'
},
{
title: 'ExAC CNVs',
token: 'exac'
}
].map(({ title, token }) => {
return {
name: title,
sourceType: 'annotation',
format: 'bed',
visibilityWindow,
displayMode: 'SQUISHED',
url: `${API_BASE_URL}grch37/${token}.bed.gz`,
indexURL: `${API_BASE_URL}grch37/${token}.bed.gz.tbi`,
color: 'black'
}
})

export const publicTracks = [
duplicationTrack,
repeatsTrack,
altTrack,
fixTrack,
hescTadTrack,
curatedMmsTrack
].concat(bgDbTracks)
2 changes: 2 additions & 0 deletions src/components/GenomeBrowserCard/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/** Alias for Genome Browser type. */
export type GenomeBrowser = any

0 comments on commit 3ecd6a1

Please sign in to comment.