Skip to content

Commit

Permalink
fixup: add quality description and q-score filters to reads
Browse files Browse the repository at this point in the history
  • Loading branch information
phildarnowsky-broad committed Jan 22, 2025
1 parent 7bd239f commit 676d412
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
GenotypeQuality,
qualityDescriptionLabels,
} from './qualityDescription'
import { qScoreLabels, QScoreBin, qScoreKeys } from './qScore'

// The 100% width/height container is necessary the component
// to size to fit its container vs staying at its initial size.
Expand All @@ -41,21 +42,6 @@ export type ScaleType =
| 'linear-truncated-1000'
| 'log'

export const qScoreKeys = [
'0',
'0.1',
'0.2',
'0.3',
'0.4',
'0.5',
'0.6',
'0.7',
'0.8',
'0.9',
'1',
] as const

export type QScoreBin = (typeof qScoreKeys)[number]
export type ColorByValue = GenotypeQuality | QScoreBin | Sex | PopulationId | ''

export type AlleleSizeDistributionItem = {
Expand Down Expand Up @@ -112,20 +98,6 @@ const colorMap: Record<ColorBy | '', Record<string, string>> = {
},
} as const

const qScoreLabels: Record<QScoreBin, string> = {
'0': '0 to 0.05',
'0.1': '0.05 to 0.15',
'0.2': '0.15 to 0.25',
'0.3': '0.25 to 0.35',
'0.4': '0.35 to 0.45',
'0.5': '0.45 to 0.55',
'0.6': '0.55 to 0.65',
'0.7': '0.65 to 0.75',
'0.8': '0.75 to 0.85',
'0.9': '0.85 to 0.95',
'1': '0.95 to 1',
}

const fixedLegendLabels: Partial<Record<ColorBy, Record<string, string>>> = {
quality_description: qualityDescriptionLabels,
q_score: qScoreLabels,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,13 @@ const ShortTandemRepeatColorBySelect = ({
}
}}
>
<option value="">None</option>
<option key="" value="">
None
</option>
{Object.entries(colorByLabels).map(([key, label]) => (
<option value={key}>{label}</option>
<option key={key} value={key}>
{label}
</option>
))}
</Select>
</Label>
Expand Down
4 changes: 2 additions & 2 deletions browser/src/ShortTandemRepeatPage/ShortTandemRepeatPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { SetStateAction, useState, Dispatch } from 'react'
import React, { useState } from 'react'
import styled from 'styled-components'

import { Badge, Button, ExternalLink, List, ListItem, Modal, Select } from '@gnomad/ui'
Expand All @@ -16,7 +16,6 @@ import ShortTandemRepeatPopulationOptions from './ShortTandemRepeatPopulationOpt
import ShortTandemRepeatColorBySelect from './ShortTandemRepeatColorBySelect'
import ShortTandemRepeatAlleleSizeDistributionPlot, {
ColorBy,
QScoreBin,
Sex,
ScaleType,
AlleleSizeDistributionItem,
Expand All @@ -38,6 +37,7 @@ import {
import ShortTandemRepeatAdjacentRepeatSection from './ShortTandemRepeatAdjacentRepeatSection'
import { PopulationId } from '@gnomad/dataset-metadata/gnomadPopulations'
import { GenotypeQuality } from './qualityDescription'
import { QScoreBin } from './qScore'

type ShortTandemRepeatReferenceRegion = {
chrom: string
Expand Down
153 changes: 126 additions & 27 deletions browser/src/ShortTandemRepeatPage/ShortTandemRepeatReads.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
Dispatch,
SetStateAction,
ChangeEvent,
} from 'react'
import styled from 'styled-components'

import { Button, Input, Select } from '@gnomad/ui'
Expand All @@ -11,7 +20,12 @@ import StatusMessage from '../StatusMessage'
import useRequest from '../useRequest'
import ControlSection from '../VariantPage/ControlSection'
import { ShortTandemRepeat } from './ShortTandemRepeatPage'
import { GenotypeQuality, qualityDescriptionLabels } from './qualityDescription'
import {
GenotypeQuality,
qualityDescriptionLabels,
genotypeQualityKeys,
} from './qualityDescription'
import { qScoreKeys, QScoreBin, qScoreLabels, QScoreBinBounds, qScoreBinBounds } from './qScore'

const ShortTandemRepeatReadImageWrapper = styled.div`
width: 100%;
Expand Down Expand Up @@ -144,7 +158,7 @@ const fetchNumReads = ({ datasetId, shortTandemRepeatId, filter }: any) => {
variables: {
datasetId,
shortTandemRepeatId,
filter,
filter: parseReadsFilter(filter),
},
}),
method: 'POST',
Expand All @@ -156,6 +170,15 @@ const fetchNumReads = ({ datasetId, shortTandemRepeatId, filter }: any) => {
.then((response) => response.data.short_tandem_repeat_reads.num_reads)
}

type ParsedReadsFilter = Omit<Filters, 'q_score'> & {
q_score: QScoreBinBounds | null
}

const parseReadsFilter = (filter: Filters): ParsedReadsFilter => {
const binBounds = filter.q_score ? qScoreBinBounds[filter.q_score] : null
return { ...filter, q_score: binBounds }
}

const fetchReads = ({ datasetId, shortTandemRepeatId, filter, limit, offset }: any) => {
return fetch('/reads/', {
body: JSON.stringify({
Expand Down Expand Up @@ -185,7 +208,7 @@ const fetchReads = ({ datasetId, shortTandemRepeatId, filter, limit, offset }: a
variables: {
datasetId,
shortTandemRepeatId,
filter,
filter: parseReadsFilter(filter),
limit,
offset,
},
Expand Down Expand Up @@ -333,11 +356,11 @@ const ShortTandemRepeatReads = ({
)
}

const ShortTandemRepeatReadsAllelesFilterControlsWrapper = styled.div`
const ShortTandemRepeatReadsFilterControlsWrapper = styled.div`
margin-bottom: 1em;
`

const ShortTandemRepeatReadsAllelesFilterControlWrapper = styled.div`
const ShortTandemRepeatReadsFilterControlWrapper = styled.div`
margin-bottom: 0.5em;
input {
Expand All @@ -349,6 +372,23 @@ const Label = styled.label`
padding-right: 1em;
`

type SharedFilters = {
population: string | null
sex: string | null
}

type Filters = SharedFilters & {
alleles:
| {
repeat_unit: string | null
min_repeats: number | null
max_repeats: number | null
}[]
| null
q_score: QScoreBin | null
quality_description: GenotypeQuality | null
}

type ShortTandemRepeatReadsAllelesFilterControlsProps = {
shortTandemRepeat: ShortTandemRepeat
value: {
Expand All @@ -357,20 +397,20 @@ type ShortTandemRepeatReadsAllelesFilterControlsProps = {
max_repeats: number | null
}[]
maxRepeats: number
onChange: (...args: any[]) => any
onChangeCallback: (...args: any[]) => any
alleleSizeDistributionRepeatUnits: string[]
}

const ShortTandemRepeatReadsAllelesFilterControls = ({
value,
maxRepeats,
onChange,
onChangeCallback,
alleleSizeDistributionRepeatUnits,
}: ShortTandemRepeatReadsAllelesFilterControlsProps) => {
return (
<ShortTandemRepeatReadsAllelesFilterControlsWrapper>
<ShortTandemRepeatReadsFilterControlsWrapper>
{[0, 1].map((alleleIndex) => (
<ShortTandemRepeatReadsAllelesFilterControlWrapper key={`${alleleIndex}`}>
<ShortTandemRepeatReadsFilterControlWrapper key={`${alleleIndex}`}>
Allele {alleleIndex + 1}: &nbsp;{' '}
{/* eslint-disable jsx-a11y/label-has-associated-control */}
{alleleSizeDistributionRepeatUnits.length > 1 && (
Expand All @@ -381,7 +421,7 @@ const ShortTandemRepeatReadsAllelesFilterControls = ({
value={value[alleleIndex].repeat_unit || ''}
onChange={(e: any) => {
const newRepeatUnit = e.target.value
onChange(
onChangeCallback(
value.map((v, i) =>
i === alleleIndex ? { ...v, repeat_unit: newRepeatUnit } : v
)
Expand All @@ -407,7 +447,7 @@ const ShortTandemRepeatReadsAllelesFilterControls = ({
value={value[alleleIndex].min_repeats}
onChange={(e: any) => {
const newMinRepeats = Math.max(Math.min(Number(e.target.value), maxRepeats), 0)
onChange(
onChangeCallback(
value.map((v, i) =>
i === alleleIndex ? { ...v, min_repeats: newMinRepeats } : v
)
Expand All @@ -425,7 +465,7 @@ const ShortTandemRepeatReadsAllelesFilterControls = ({
value={value[alleleIndex].max_repeats}
onChange={(e: any) => {
const newMaxRepeats = Math.max(Math.min(Number(e.target.value), maxRepeats), 0)
onChange(
onChangeCallback(
value.map((v, i) =>
i === alleleIndex ? { ...v, max_repeats: newMaxRepeats } : v
)
Expand All @@ -434,28 +474,80 @@ const ShortTandemRepeatReadsAllelesFilterControls = ({
/>
</Label>
{/* eslint-enable jsx-a11y/label-has-associated-control */}
</ShortTandemRepeatReadsAllelesFilterControlWrapper>
</ShortTandemRepeatReadsFilterControlWrapper>
))}
</ShortTandemRepeatReadsAllelesFilterControlsWrapper>
</ShortTandemRepeatReadsFilterControlsWrapper>
)
}

type Filters = {
population: string | null
sex: string | null
alleles:
| {
repeat_unit: string | null
min_repeats: number | null
max_repeats: number | null
}[]
| null
type ShortTandemRepeatReadsQualityFilterControlsProps = {
shortTandemRepeat: ShortTandemRepeat
filter: Filters
setFilter: Dispatch<SetStateAction<Filters>>
}

const ShortTandemRepeatReadsQualityFilterControls = ({
filter,
setFilter,
}: ShortTandemRepeatReadsQualityFilterControlsProps) => {
return (
<ShortTandemRepeatReadsFilterControlsWrapper>
<ShortTandemRepeatReadsFilterControlWrapper key="manual-review">
<Label htmlFor="short-tandem-repeat-reads-manual-review-filter">
Manual review: &nbsp;
{/* @ts-expect-error TS(2769) FIXME: No overload matches this call. */}
<Select
id="short-tandem-repeat-reads-manual-review-filter"
value={filter.quality_description || ''}
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
const newValue =
e.currentTarget.value == '' ? null : (e.currentTarget.value as GenotypeQuality)
setFilter({ ...filter, quality_description: newValue })
}}
>
<option key="any" value="">
Any
</option>
{genotypeQualityKeys.map((genotypeQualityKey) => (
<option key={genotypeQualityKey} value={genotypeQualityKey}>
{qualityDescriptionLabels[genotypeQualityKey]}
</option>
))}
</Select>
</Label>{' '}
</ShortTandemRepeatReadsFilterControlWrapper>
<ShortTandemRepeatReadsFilterControlWrapper key="q-score">
<Label htmlFor="short-tandem-repeat-reads-q-score-filter">
Q-score: &nbsp;
{/* @ts-expect-error TS(2769) FIXME: No overload matches this call. */}
<Select
id="short-tandem-repeat-reads-q-score-filter"
value={filter.q_score || ''}
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
const newValue =
e.currentTarget.value == '' ? null : (e.currentTarget.value as QScoreBin)
setFilter({ ...filter, q_score: newValue })
}}
>
<option key="any" value="">
Any
</option>
{qScoreKeys.map((qScoreKey) => (
<option key={qScoreKey} value={qScoreKey}>
{qScoreLabels[qScoreKey]}
</option>
))}
</Select>
</Label>
</ShortTandemRepeatReadsFilterControlWrapper>
</ShortTandemRepeatReadsFilterControlsWrapper>
)
}

type ShortTandemRepeatReadsContainerProps = {
datasetId: string
shortTandemRepeat: ShortTandemRepeat
filter: Omit<Filters, 'alleles'>
filter: SharedFilters
maxRepeats: number
alleleSizeDistributionRepeatUnits: string[]
}
Expand Down Expand Up @@ -487,6 +579,8 @@ const ShortTandemRepeatReadsContainer = ({
max_repeats: maxRepeats,
},
],
q_score: null,
quality_description: null,
})

if (baseFilter.population !== filter.population || baseFilter.sex !== filter.sex) {
Expand All @@ -501,12 +595,17 @@ const ShortTandemRepeatReadsContainer = ({
<ShortTandemRepeatReadsAllelesFilterControls
shortTandemRepeat={shortTandemRepeat}
value={filter.alleles || []}
onChange={(newAllelesFilter) => {
onChangeCallback={(newAllelesFilter) => {
setFilter((prevFilter) => ({ ...prevFilter, alleles: newAllelesFilter }))
}}
maxRepeats={maxRepeats}
alleleSizeDistributionRepeatUnits={alleleSizeDistributionRepeatUnits}
/>
<ShortTandemRepeatReadsQualityFilterControls
shortTandemRepeat={shortTandemRepeat}
filter={filter}
setFilter={setFilter}
/>
<ShortTandemRepeatReads
datasetId={datasetId}
shortTandemRepeat={shortTandemRepeat}
Expand Down
Loading

0 comments on commit 676d412

Please sign in to comment.