Skip to content

Commit

Permalink
feat: display ClinVar and GTEx values for each gene (#672, #68) (#1129)
Browse files Browse the repository at this point in the history
  • Loading branch information
holtgrewe authored Sep 13, 2023
1 parent 1f3e4a2 commit 6c83a00
Show file tree
Hide file tree
Showing 8 changed files with 560 additions and 37 deletions.
45 changes: 45 additions & 0 deletions varfish/vueapp/src/varfish/api/annonars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,51 @@ export class AnnonarsApiClient {
return result
}

/**
* 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)
}
})
return result
}

/**
* Retrieve variant information via annonars REST API.
*/
Expand Down
13 changes: 10 additions & 3 deletions varfish/vueapp/src/varfish/components/VegaPlot.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const props = defineProps({
},
encoding: Object,
params: Object,
layer: Object,
width: {
type: [Number, String],
default: 300,
Expand All @@ -29,7 +30,7 @@ const props = defineProps({
default: 300,
},
mark: {
type: Object,
type: [Boolean, Object],
default: { type: 'bar' },
},
renderer: {
Expand All @@ -46,7 +47,7 @@ const vegaViewRef = ref(null)
/** The vega specification. */
const vegaLiteSpec = computed(() => {
return {
const res = {
$schema: 'https://vega.github.io/schema/vega-lite/v5.json',
width: props.width,
height: props.height,
Expand All @@ -56,10 +57,16 @@ const vegaLiteSpec = computed(() => {
values: props.dataValues,
name: props.dataName,
},
mark: props.mark,
encoding: props.encoding,
transform: props.transform,
}
if (props.mark !== undefined && props.mark !== null && props.mark !== false) {
res.mark = props.mark
}
if (props.layer !== undefined && props.layer !== null) {
res.layer = props.layer
}
return res
})
/** Make component reactive to props.data changes. */
Expand Down
1 change: 1 addition & 0 deletions variants/vueapp/src/components/VariantDetails.vue
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ onMounted(() => {
<SimpleCard id="gene" title="Gene">
<VariantDetailsGene
:gene="variantDetailsStore.gene"
:geneClinvar="variantDetailsStore.geneClinvar"
:small-var="variantDetailsStore.smallVariant"
:hgmd-pro-enabled="queryStore.hgmdProEnabled"
:hgmd-pro-prefix="queryStore.hgmdProPrefix"
Expand Down
47 changes: 24 additions & 23 deletions variants/vueapp/src/components/VariantDetails/Clinvar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,26 @@ import { useVariantDetailsStore } from '@variants/stores/variantDetails'
const variantDetailsStore = useVariantDetailsStore()
const clinvar = computed(() => variantDetailsStore?.varAnnos?.clinvar)
const interpretations = [
'N/A',
'Benign',
'Likely benign',
'Uncertain signifiance',
'Likely pathogenic',
'Pathogenic',
const clinicalSignificanceLabel = [
'pathogenic', // 0
'likely pathogenic', // 1
'uncertain signifiance', // 2
'likely benign', // 3
'benign', // 4
'other',
]
const reviewStatus = (goldStars: number): string => {
const res = []
for (let i = 1; i <= 5; i++) {
res.push()
}
return res.join('')
}
const reviewStatusLabel = [
'no assertion provided', // 0
'no assertion criteria provided', // 1
'criteria provided, conflicting interpretations', // 2
'criteria provided, single submitter', // 3
'criteria provided, multiple submitters, no conflicts', // 4
'reviewed by expert panel', // 5
'practice guideline', // 6
]
const reviewStatusStars = [0, 0, 0, 1, 2, 3, 4]
</script>

<template>
Expand All @@ -32,30 +36,27 @@ const reviewStatus = (goldStars: number): string => {
information. The link-outs to NCBI ClinVar will display the most current
data that may differ from our "frozen" copy.
</div>
<div v-if="clinvar?.vcv">
<div v-if="clinvar?.rcv">
<div>
<strong>Interpretation: </strong>
{{
clinvar.summary_clinvar_pathogenicity
.map((num) => interpretations[num])
.join(', ')
}}
{{ clinicalSignificanceLabel[clinvar.clinical_significance] }}
</div>
<div>
<strong>Review Status: </strong>
<template v-for="i of [1, 2, 3, 4, 5]">
<i-mdi-star v-if="i <= clinvar.summary_clinvar_gold_stars" />
<i-mdi-star v-if="i <= reviewStatusStars[clinvar.review_status]" />
<i-mdi-star-outline v-else />
</template>
{{ reviewStatusLabel[clinvar.review_status] }}
</div>
<div>
<strong>Accession: </strong>
<a
:href="`https://www.ncbi.nlm.nih.gov/clinvar/?term=${clinvar.vcv}`"
:href="`https://www.ncbi.nlm.nih.gov/clinvar/${clinvar.rcv}/`"
target="_blank"
>
<i-mdi-launch />
{{ clinvar.vcv }}
{{ clinvar.rcv }}
</a>
</div>
</div>
Expand Down
137 changes: 137 additions & 0 deletions variants/vueapp/src/components/VariantDetails/ClinvarFreqPlot.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
<script setup lang="ts">
import * as vega from 'vega'
import { computed, ref } from 'vue'
import VegaPlot from '@varfish/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>
Loading

0 comments on commit 6c83a00

Please sign in to comment.