Skip to content

Commit

Permalink
feat: ClinVar and GTEx info cards
Browse files Browse the repository at this point in the history
  • Loading branch information
gromdimon committed Sep 14, 2023
1 parent eb428d1 commit 02cb3cf
Show file tree
Hide file tree
Showing 11 changed files with 1,513 additions and 151 deletions.
897 changes: 877 additions & 20 deletions frontend/package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
"@mdi/font": "^7.2.96",
"pinia": "^2.1.6",
"resize-observer-polyfill": "^1.5.1",
"vega": "^5.25.0",
"vega-embed": "^6.22.2",
"vue": "^3.3.4",
"vue-router": "^4.2.4",
"vuetify": "^3.3.14"
Expand Down
47 changes: 4 additions & 43 deletions frontend/src/api/annonars.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { chunks } from '@reactgular/chunks'
import { API_BASE_PREFIX_ANNONARS } from '@/api/common'

const API_BASE_URL = `${API_BASE_PREFIX_ANNONARS}/`
Expand Down Expand Up @@ -42,49 +41,11 @@ export class AnnonarsClient {
return await response.json()
}

/**
* Retrieve clinvar-gene information via annonars REST API.
*
* @param hgncIds Array of HGNC IDs to use, e.g., `["HGNC:26467"]`.
* @param chunkSize How many IDs to send in one request.
* @returns Promise with an array of gene information objects.
*/
async retrieveGeneClinvarInfos(
hgncIds: Array<string>,
chunkSize?: number,
): Promise<Array<any>> {
const hgncIdChunks = chunks(hgncIds, chunkSize ?? this.defaultChunkSize)

const promises = hgncIdChunks.map((chunk) => {
const url = `${this.baseUrl}/genes/clinvar?hgnc_id=${chunk.join(',')}`

const headers = {
Accept: 'application/json',
'Content-Type': 'application/json',
}
if (this.csrfToken) {
headers['X-CSRFToken'] = this.csrfToken
}

return fetch(url, {
method: 'GET',
credentials: 'same-origin',
headers,
})
})

const responses = await Promise.all(promises)
const results = await Promise.all(
responses.map((response) => response.json()),
)

const result = []
results.forEach((chunk) => {
for (const value of Object.values(chunk.genes)) {
result.push(value)
}
async fetchGeneClinvarInfo(hgncId: string): Promise<any> {
const response = await fetch(`${this.apiBaseUrl}genes/clinvar?hgnc_id=${hgncId}`, {
method: 'GET'
})
return result
return await response.json()
}

async fetchGenes(query: string): Promise<any> {
Expand Down
136 changes: 136 additions & 0 deletions frontend/src/components/ClinVarFreqPlot.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<script setup lang="ts">
import { computed } from 'vue'
import VegaPlot from '@/components/VegaPlot.vue'
const coarseClinsigLabels = ['benign', 'uncertain', 'pathogenic']
const bucketLabels = [
'no frequency',
'<0.00001',
'<0.00025',
'<0.0005',
'<0.0001',
'<0.00025',
'<0.0005',
'<0.001',
'<0.0025',
'<0.005',
'<0.01',
'<0.025',
'<0.05',
'<0.1',
'<0.25',
'<0.5',
'<1.0'
]
export interface CountsRecord {
/** Coarse clinical significance ID */
coarse_clinsig: number
/** Counts per bucket */
counts: number[]
}
export interface Props {
/** Gene symbol */
geneSymbol: string
/** Expression records */
perFreqCounts: CountsRecord[]
}
const props = withDefaults(defineProps<Props>(), {})
const vegaData = computed(() => {
const values = []
for (const record of props?.perFreqCounts || []) {
for (let i = 0; i < record.counts.length; i++) {
if (record.counts[i] > 0) {
values.push({
coarseClinsig: coarseClinsigLabels[record.coarse_clinsig],
coarseClinsigNo: record.coarse_clinsig,
freqBucket: bucketLabels[i],
freqBucketNo: i,
value: record.counts[i]
})
}
}
}
if (values.length) {
return values
} else {
return null
}
})
const vegaLayer = [
{
mark: { type: 'bar', tooltip: true }
},
{
mark: {
type: 'text',
align: 'center',
baseline: 'middle',
dy: -10
},
encoding: {
text: { field: 'value', type: 'quantitative', fontSize: 8 }
}
}
]
const vegaEncoding = {
x: {
field: 'freqBucket',
title: 'population frequency',
type: 'nominal',
sort: bucketLabels,
axis: { labelAngle: 45 }
},
y: {
field: 'value',
scale: { type: 'log' },
title: 'variant count',
axis: {
grid: false,
tickExtra: false
}
},
xOffset: {
field: 'coarseClinsig',
type: 'nominal',
sort: coarseClinsigLabels
},
color: {
field: 'coarseClinsig',
title: 'clinical sig.',
type: 'nominal',
sort: coarseClinsigLabels,
scale: {
domain: coarseClinsigLabels,
range: ['#5d9936', '#f5c964', '#b05454']
}
}
}
/** Ref to the VegaPlot (for testing). */
// const vegaPlotRef = ref(null)
</script>

<template>
<figure class="figure border rounded pl-2 pt-2 mr-3 w-100 col">
<figcaption class="figure-caption text-center">
Population frequency of ClinVar variants
</figcaption>
<VegaPlot
:data-values="vegaData"
:encoding="vegaEncoding"
:mark="false"
:layer="vegaLayer"
:width="800"
:height="300"
renderer="svg"
/>
</figure>
</template>
188 changes: 188 additions & 0 deletions frontend/src/components/GtexGenePlot.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
<script setup lang="ts">
import { computed } from 'vue'
import VegaPlot from '@/components/VegaPlot.vue'
export interface ExpressionRecord {
/** Tissue id */
tissue: number
/** Detailed tissue id */
tissue_detailed: number
/** TPM quantiles (0, 0.25, 0.5, 0.75, 1.0) */
tpms: number[]
}
export interface Props {
/** Gene symbol */
geneSymbol: string
/** Expression records */
expressionRecords: ExpressionRecord[]
}
const props = withDefaults(defineProps<Props>(), {})
const tissueLabels = [
'Adipose Tissue',
'Adrenal Gland',
'Bladder',
'Blood',
'Blood Vessel',
'Bone Marrow',
'Brain',
'Breast',
'Cervix Uteri',
'Colon',
'Esophagus',
'Fallopian Tube',
'Heart',
'Kidney',
'Liver',
'Lung',
'Muscle',
'Nerve',
'Ovary',
'Pancreas',
'Pituitary',
'Prostate',
'Salivary Gland',
'Skin',
'Small Intestine',
'Spleen',
'Stomach',
'Testis',
'Thyroid',
'Uterus',
'Vagina'
]
const tissueDetailedLabels = [
'Adipose - Subcutaneous',
'Adipose - Visceral (Omentum)',
'Adrenal Gland',
'Artery - Aorta',
'Artery - Coronary',
'Artery - Tibial',
'Bladder',
'Brain - Amygdala',
'Brain - Anterior cingulate cortex (BA24)',
'Brain - Caudate (basal ganglia)',
'Brain - Cerebellar Hemisphere',
'Brain - Cerebellum',
'Brain - Cortex',
'Brain - Frontal Cortex (BA9)',
'Brain - Hippocampus',
'Brain - Hypothalamus',
'Brain - Nucleus accumbens (basal ganglia)',
'Brain - Putamen (basal ganglia)',
'Brain - Spinal cord (cervical c-1)',
'Brain - Substantia nigra',
'Breast - Mammary Tissue',
'Cells - Cultured fibroblasts',
'Cells - EBV-transformed lymphocytes',
'Cells - Leukemia cell line (CML)',
'Cervix - Ectocervix',
'Cervix - Endocervix',
'Colon - Sigmoid',
'Colon - Transverse',
'Esophagus - Gastroesophageal Junction',
'Esophagus - Mucosa',
'Esophagus - Muscularis',
'Fallopian Tube',
'Heart - Atrial Appendage',
'Heart - Left Ventricle',
'Kidney - Cortex',
'Kidney - Medulla',
'Liver',
'Lung',
'Minor Salivary Gland',
'Muscle - Skeletal',
'Nerve - Tibial',
'Ovary',
'Pancreas',
'Pituitary',
'Prostate',
'Salivary Gland',
'Skin - Not Sun Exposed (Suprapubic)',
'Skin - Sun Exposed (Lower leg)',
'Small Intestine - Terminal Ileum',
'Spleen',
'Stomach',
'Testis',
'Thyroid',
'Uterus',
'Vagina',
'Whole Blood'
]
const vegaData = computed(() => {
return props?.expressionRecords?.map((record) => ({
tissue: tissueLabels[record.tissue],
tissueDetailed: tissueDetailedLabels[record.tissue_detailed],
lower: record.tpms[0],
q1: record.tpms[1],
median: record.tpms[2],
q3: record.tpms[3],
upper: record.tpms[4]
}))
})
const vegaEncoding = {
x: {
field: 'tissueDetailed',
type: 'nominal',
title: null,
axis: { labelAngle: 45 }
}
}
const vegaLayer = [
{
mark: { type: 'rule', tooltip: { content: 'data' } },
encoding: {
y: {
field: 'lower',
type: 'quantitative',
scale: { zero: false },
title: 'TPM'
},
y2: { field: 'upper' }
}
},
{
mark: { type: 'bar', size: 14, tooltip: { content: 'data' } },
encoding: {
y: { field: 'q1', type: 'quantitative' },
y2: { field: 'q3' },
color: { field: 'tissue', type: 'nominal', legend: null }
}
},
{
mark: {
type: 'tick',
color: 'white',
size: 14,
tooltip: { content: 'data' }
},
encoding: {
y: { field: 'median', type: 'quantitative' }
}
}
]
</script>

<template>
<figure class="figure border rounded pl-2 pt-2 mr-3 w-100 col">
<figcaption class="figure-caption text-center">
Bulk tissue gene expression for gene {{ props.geneSymbol }}
</figcaption>
<VegaPlot
:data-values="vegaData"
:encoding="vegaEncoding"
:layer="vegaLayer"
:mark="false"
:width="1000"
:height="300"
renderer="svg"
/>
</figure>
</template>
Loading

0 comments on commit 02cb3cf

Please sign in to comment.