diff --git a/.gitignore b/.gitignore index 6c612328a..0835549af 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,6 @@ hail-*.log # Playright test dirs /tests/playwright/ /playwright/.cache/ + +# Reads metadata databases +reads/*.db diff --git a/browser/package.json b/browser/package.json index 77803e4e3..adbd20a93 100644 --- a/browser/package.json +++ b/browser/package.json @@ -23,6 +23,9 @@ "@gnomad/ui": "2.0.0", "@hot-loader/react-dom": "^17.0.0", "@visx/axis": "^3.0.0", + "@visx/legend": "^3.12.0", + "@visx/scale": "^3.12.0", + "@visx/shape": "^3.12.0", "core-js": "3.5.0", "css-loader": "^6.7.3", "d3-array": "^1.2.4", diff --git a/browser/src/GenePage/GenePage.tsx b/browser/src/GenePage/GenePage.tsx index 15be9baa8..2cbca65c2 100644 --- a/browser/src/GenePage/GenePage.tsx +++ b/browser/src/GenePage/GenePage.tsx @@ -338,7 +338,7 @@ const GenePage = ({ datasetId, gene, geneId }: Props) => { tandem repeat locus {' '} - in this gene. + in this gene

)} diff --git a/browser/src/GenePage/GenePageContainer.tsx b/browser/src/GenePage/GenePageContainer.tsx index e78711c20..313e223f7 100644 --- a/browser/src/GenePage/GenePageContainer.tsx +++ b/browser/src/GenePage/GenePageContainer.tsx @@ -263,7 +263,6 @@ query ${operationName}($geneId: String, $geneSymbol: String, $referenceGenome: R } } ` - type Props = { datasetId: DatasetId geneIdOrSymbol: string diff --git a/browser/src/Query.tsx b/browser/src/Query.tsx index f07cab204..dfe01452d 100644 --- a/browser/src/Query.tsx +++ b/browser/src/Query.tsx @@ -84,6 +84,7 @@ export class BaseQuery extends Component { loadData() { const { operationName, query, url, variables } = this.props + console.log('Loading url:', url) this.setState({ loading: true, error: null, diff --git a/browser/src/RegionPage/RegionPageContainer.tsx b/browser/src/RegionPage/RegionPageContainer.tsx index 45da7c9a5..c24eaf0f3 100644 --- a/browser/src/RegionPage/RegionPageContainer.tsx +++ b/browser/src/RegionPage/RegionPageContainer.tsx @@ -47,7 +47,6 @@ const query = ` } } ` - type Props = { datasetId: DatasetId regionId: string diff --git a/browser/src/ShortTandemRepeatPage/ShortTandemRepeatAdjacentRepeatSection.tsx b/browser/src/ShortTandemRepeatPage/ShortTandemRepeatAdjacentRepeatSection.tsx index 81c9bcde4..06c073bcf 100644 --- a/browser/src/ShortTandemRepeatPage/ShortTandemRepeatAdjacentRepeatSection.tsx +++ b/browser/src/ShortTandemRepeatPage/ShortTandemRepeatAdjacentRepeatSection.tsx @@ -1,5 +1,4 @@ -import { max, min } from 'd3-array' -import React, { useState } from 'react' +import React, { SetStateAction, useState, Dispatch } from 'react' import { Modal, Select } from '@gnomad/ui' @@ -7,6 +6,7 @@ import ControlSection from '../VariantPage/ControlSection' import ShortTandemRepeatPopulationOptions from './ShortTandemRepeatPopulationOptions' import { ShortTandemRepeatAdjacentRepeat } from './ShortTandemRepeatPage' +import { ScaleType, Sex, ColorBy } from './ShortTandemRepeatAlleleSizeDistributionPlot' import ShortTandemRepeatAlleleSizeDistributionPlot from './ShortTandemRepeatAlleleSizeDistributionPlot' import ShortTandemRepeatGenotypeDistributionPlot from './ShortTandemRepeatGenotypeDistributionPlot' import ShortTandemRepeatGenotypeDistributionBinDetails from './ShortTandemRepeatGenotypeDistributionBinDetails' @@ -16,37 +16,52 @@ import { getSelectedAlleleSizeDistribution, getSelectedGenotypeDistribution, getGenotypeDistributionPlotAxisLabels, + genotypeRepunitPairs, + maxAlleleSizeDistributionRepeats, + maxGenotypeDistributionRepeats, } from './shortTandemRepeatHelpers' +import { PopulationId } from '@gnomad/dataset-metadata/gnomadPopulations' +import { Bin as GenotypeBin } from './ShortTandemRepeatGenotypeDistributionPlot' type Props = { adjacentRepeat: ShortTandemRepeatAdjacentRepeat - populationIds: string[] - selectedPopulationId: string - onSelectPopulationId: (...args: any[]) => any - selectedScaleType: string - onSelectScaleType: (...args: any[]) => any + selectedScaleType: ScaleType + selectedPopulation: PopulationId | '' + selectedSex: Sex | '' + selectedColorBy: ColorBy | '' + populations: PopulationId[] + selectedGenotypeDistributionBin: GenotypeBin | null + setSelectedGenotypeDistributionBin: Dispatch> + setSelectedScaleType: Dispatch> + setSelectedPopulation: Dispatch> + setSelectedSex: Dispatch> + setSelectedColorBy: (newColorBy: ColorBy | '') => void } const ShortTandemRepeatAdjacentRepeatSection = ({ adjacentRepeat, - populationIds, - selectedPopulationId, - onSelectPopulationId, + populations, selectedScaleType, - onSelectScaleType, + selectedPopulation, + selectedSex, + selectedColorBy, + setSelectedScaleType, + setSelectedPopulation, + setSelectedSex, + setSelectedColorBy, }: Props) => { const [selectedRepeatUnit, setSelectedRepeatUnit] = useState( adjacentRepeat.repeat_units.length === 1 ? adjacentRepeat.repeat_units[0] : '' ) + const genotypeDistributionPairs = genotypeRepunitPairs(adjacentRepeat) + const defaultGenotypeDistributionRepeatUnits = + genotypeDistributionPairs.length === 1 ? genotypeDistributionPairs[0] : '' const [selectedGenotypeDistributionRepeatUnits, setSelectedGenotypeDistributionRepeatUnits] = - useState( - adjacentRepeat.genotype_distribution.repeat_units.length === 1 - ? adjacentRepeat.genotype_distribution.repeat_units[0].repeat_units.join(' / ') - : '' - ) + useState(defaultGenotypeDistributionRepeatUnits) - const [selectedGenotypeDistributionBin, setSelectedGenotypeDistributionBin] = useState(null) + const [selectedGenotypeDistributionBin, setSelectedGenotypeDistributionBin] = + useState(null) return (
@@ -55,25 +70,25 @@ const ShortTandemRepeatAdjacentRepeatSection = ({

Allele Size Distribution

Genotype Distribution

max(d.slice(0, 2))), - max(adjacentRepeat.genotype_distribution.distribution, (d: any) => min(d.slice(0, 2))), - ]} + maxRepeats={maxGenotypeDistributionRepeats(adjacentRepeat)} genotypeDistribution={getSelectedGenotypeDistribution(adjacentRepeat, { selectedRepeatUnits: selectedGenotypeDistributionRepeatUnits, - selectedPopulationId, + selectedPopulation: selectedPopulation, + selectedSex: selectedSex, })} - onSelectBin={(bin: any) => { + onSelectBin={(bin: GenotypeBin) => { if (bin.xRange[0] !== bin.xRange[1] || bin.yRange[0] !== bin.yRange[1]) { setSelectedGenotypeDistributionBin(bin) } }} + xRanges={[]} + yRanges={[]} /> {selectedGenotypeDistributionBin && ( )} diff --git a/browser/src/ShortTandemRepeatPage/ShortTandemRepeatAgeDistributionPlot.tsx b/browser/src/ShortTandemRepeatPage/ShortTandemRepeatAgeDistributionPlot.tsx index 8fa875668..c328be5e8 100644 --- a/browser/src/ShortTandemRepeatPage/ShortTandemRepeatAgeDistributionPlot.tsx +++ b/browser/src/ShortTandemRepeatPage/ShortTandemRepeatAgeDistributionPlot.tsx @@ -1,12 +1,12 @@ import { max } from 'd3-array' import { scaleBand, scaleLog } from 'd3-scale' -import PropTypes from 'prop-types' import React from 'react' import { withSize } from 'react-sizeme' import styled from 'styled-components' import { AxisBottom, AxisLeft } from '@visx/axis' import { TooltipAnchor } from '@gnomad/ui' +import { PlotRange, AgeDistributionItem } from './ShortTandemRepeatPage' // The 100% width/height container is necessary the component // to size to fit its container vs staying at its initial size. @@ -19,9 +19,9 @@ const GraphWrapper = styled.div` const labelProps = { fontSize: 14, textAnchor: 'middle', -} +} as const -const ageRangeLabel = (ageRange: any) => { +const ageRangeLabel = (ageRange: [number | null, number | null]) => { const [minAge, maxAge] = ageRange if (minAge === null) { @@ -33,9 +33,15 @@ const ageRangeLabel = (ageRange: any) => { return `${minAge}-${maxAge}` } +type Props = { + ageDistribution: AgeDistributionItem[] + maxRepeats: number + ranges: PlotRange[] + size: { width: number } +} + const ShortTandemRepeatAgeDistributionPlot = withSize()( - // @ts-expect-error TS(2339) FIXME: Property 'ageDistribution' does not exist on type ... Remove this comment to see the full error message - ({ ageDistribution, maxRepeats, ranges, size: { width } }) => { + ({ ageDistribution, maxRepeats, ranges = [], size: { width } }: Props) => { const height = Math.min(width, 300) const margin = { @@ -53,7 +59,7 @@ const ShortTandemRepeatAgeDistributionPlot = withSize()( const yNumBins = ageDistribution.length - const data = Array.from(Array(xNumBins * yNumBins).keys()).map((n: any) => { + const data = Array.from(Array(xNumBins * yNumBins).keys()).map((n) => { const xBinIndex = Math.floor(n / yNumBins) const yBinIndex = n % yNumBins @@ -76,22 +82,19 @@ const ShortTandemRepeatAgeDistributionPlot = withSize()( } }) - ageDistribution.forEach((ageBin: any, yBinIndex: any) => { - // @ts-expect-error TS(7031) FIXME: Binding element 'repeats' implicitly has an 'any' ... Remove this comment to see the full error message + ageDistribution.forEach((ageBin, yBinIndex) => { ageBin.distribution.forEach(([repeats, nAlleles]) => { const xBinIndex = Math.floor(repeats / xBinSize) data[xBinIndex * yNumBins + yBinIndex].count += nAlleles }) }) - const xScale = scaleBand() - // @ts-expect-error TS(2345) FIXME: Argument of type 'number[]' is not assignable to p... Remove this comment to see the full error message + const xScale = scaleBand() .domain(Array.from(Array(xNumBins).keys())) .range([0, plotWidth]) const xBandwidth = xScale.bandwidth() - const yScale = scaleBand() - // @ts-expect-error TS(2345) FIXME: Argument of type 'number[]' is not assignable to p... Remove this comment to see the full error message + const yScale = scaleBand() .domain(Array.from(Array(yNumBins).keys())) .range([plotHeight, 0]) const yBandwidth = yScale.bandwidth() @@ -99,7 +102,7 @@ const ShortTandemRepeatAgeDistributionPlot = withSize()( const xMaxNumLabels = Math.floor(plotWidth / 20) const xLabelInterval = Math.max(Math.round(xNumBins / xMaxNumLabels), 1) - const xTickFormat = (binIndex: any) => { + const xTickFormat = (binIndex: number) => { if (binIndex % xLabelInterval !== 0) { return '' } @@ -111,16 +114,12 @@ const ShortTandemRepeatAgeDistributionPlot = withSize()( return `${binIndex * xBinSize} - ${binIndex * xBinSize + xBinSize - 1}` } - const yTickFormat = (binIndex: any) => { + const yTickFormat = (binIndex: number) => { return ageRangeLabel(ageDistribution[binIndex].age_range) } const opacityScale = scaleLog() - // @ts-expect-error TS(2345) FIXME: Argument of type '(string | number | undefined)[]'... Remove this comment to see the full error message - .domain([ - 1, - max(ageDistribution, (ageBin: any) => max(ageBin.distribution, (d: any) => d[1])), - ]) + .domain([1, max(ageDistribution, (ageBin) => max(ageBin.distribution, (d) => d[1])) || 2]) .range([0.1, 1]) return ( @@ -129,7 +128,6 @@ const ShortTandemRepeatAgeDistributionPlot = withSize()( {data - .filter((d: any) => d.count !== 0) - .map((d: any) => { + .filter((d) => d.count !== 0) + .map((d) => { return ( {ranges - .filter((range: any) => range.start !== range.stop) - .filter((range: any) => range.start <= maxRepeats) - .map((range: any, rangeIndex: any) => { + .filter((range) => range.start !== range.stop) + .filter((range) => range.start <= maxRepeats) + .map((range, rangeIndex) => { const startBinIndex = Math.floor(range.start / xBinSize) const startX = - // @ts-expect-error TS(2532) FIXME: Object is possibly 'undefined'. - xScale(startBinIndex) + + (xScale(startBinIndex) || 0) + ((range.start - startBinIndex * xBinSize) / xBinSize) * xBandwidth let stopX if (range.stop <= maxRepeats) { const stopBinIndex = Math.floor(range.stop / xBinSize) stopX = - // @ts-expect-error TS(2532) FIXME: Object is possibly 'undefined'. - xScale(stopBinIndex) + + (xScale(stopBinIndex) || 0) + ((range.stop - stopBinIndex * xBinSize) / xBinSize) * xBandwidth } else { stopX = plotWidth @@ -306,27 +300,4 @@ const ShortTandemRepeatAgeDistributionPlot = withSize()( ShortTandemRepeatAgeDistributionPlot.displayName = 'ShortTandemRepeatAgeDistributionPlot' -ShortTandemRepeatAgeDistributionPlot.propTypes = { - // @ts-expect-error TS(2322) FIXME: Type '{ ageDistribution: PropTypes.Requireable<(Pr... Remove this comment to see the full error message - ageDistribution: PropTypes.arrayOf( - PropTypes.shape({ - age_range: PropTypes.arrayOf(PropTypes.number).isRequired, - distribution: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.number)).isRequired, - }) - ), - maxRepeats: PropTypes.number.isRequired, - ranges: PropTypes.arrayOf( - PropTypes.shape({ - start: PropTypes.number.isRequired, - stop: PropTypes.number.isRequired, - label: PropTypes.string.isRequired, - }) - ), -} - -ShortTandemRepeatAgeDistributionPlot.defaultProps = { - // @ts-expect-error TS(2322) FIXME: Type '{ ranges: never[]; }' is not assignable to t... Remove this comment to see the full error message - ranges: [], -} - export default ShortTandemRepeatAgeDistributionPlot diff --git a/browser/src/ShortTandemRepeatPage/ShortTandemRepeatAlleleSizeDistributionPlot.tsx b/browser/src/ShortTandemRepeatPage/ShortTandemRepeatAlleleSizeDistributionPlot.tsx index 392583544..e7c45c7a5 100644 --- a/browser/src/ShortTandemRepeatPage/ShortTandemRepeatAlleleSizeDistributionPlot.tsx +++ b/browser/src/ShortTandemRepeatPage/ShortTandemRepeatAlleleSizeDistributionPlot.tsx @@ -1,12 +1,16 @@ import { max } from 'd3-array' -import { scaleBand, scaleLinear, scaleLog } from 'd3-scale' -import PropTypes from 'prop-types' +import { scaleBand, scaleLinear, scaleLog, scaleOrdinal } from 'd3-scale' import React, { useMemo } from 'react' import { withSize } from 'react-sizeme' import styled from 'styled-components' import { AxisBottom, AxisLeft } from '@visx/axis' +import { BarStack, Bar } from '@visx/shape' +import { AnyD3Scale } from '@visx/scale' +import { LegendOrdinal } from '@visx/legend' import { TooltipAnchor } from '@gnomad/ui' +import { GNOMAD_POPULATION_NAMES } from '@gnomad/dataset-metadata/gnomadPopulations' +import { PopulationId } from '@gnomad/dataset-metadata/gnomadPopulations' // The 100% width/height container is necessary the component // to size to fit its container vs staying at its initial size. @@ -16,15 +20,139 @@ const GraphWrapper = styled.div` height: 100%; /* stylelint-disable-line unit-whitelist */ ` -const TooltipTrigger = styled.rect` +const BarWithHoverEffect = styled(Bar)` pointer-events: visible; &:hover { - fill: rgba(0, 0, 0, 0.05); + fill-opacity: 0.7; } ` -const tickFormat = (n: any) => { +export type ScaleType = + | 'linear' + | 'linear-truncated-50' + | 'linear-truncated-200' + | 'linear-truncated-1000' + | 'log' + +export const genotypeQualityKeys = [ + 'low', + 'medium-low', + 'medium', + 'medium-high', + 'high', + 'not-reviewed', +] as const + +export type GenotypeQuality = (typeof genotypeQualityKeys)[number] + +export const qScoreKeys = [ + '0.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 = { + repunit_count: number + frequency: number + colorByValue: ColorByValue +} + +export type Sex = 'XX' | 'XY' + +export type ColorBy = 'quality_description' | 'q_score' | 'population' | 'sex' + +const defaultColor = '#73ab3d' +const colorMap: Record> = { + '': { + '': defaultColor, + }, + quality_description: { + low: '#d73027', + 'medium-low': '#fc8d59', + medium: '#fee08b', + 'medium-high': '#d9ef8b', + high: '#1a9850', + 'not-reviewed': '#aaaaaa', + }, + q_score: { + '0.0': '#ff0000', + '0.1': '#ff3300', + '0.2': '#ff6600', + '0.3': '#ff9900', + '0.4': '#ffcc00', + '0.5': '#ffff00', + '0.6': '#ccff33', + '0.7': '#99ff66', + '0.8': '#66ff99', + '0.9': '#33ffcc', + '1': '#00ff00', + }, + sex: { + XX: '#F7C3CC', + XY: '#6AA6CE', + }, + population: { + nfe: '#6AA6CE', + afr: '#941494', + fin: '#012F6C', + amr: '#EF1E24', + ami: '#ff7f00', + asj: '#FF7E4F', + eas: '#128B44', + mid: '#f781bf', + oth: '#ABB8B9', + sas: '#FE9A10', + }, +} as const + +const qualityDescriptionLabels: Record = { + low: 'Low', + 'medium-low': 'Medium-low', + medium: 'Medium', + 'medium-high': 'Medium-high', + high: 'High', + 'not-reviewed': 'Not reviewed', +} + +const qScoreLabels: Record = { + '0.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>> = { + quality_description: qualityDescriptionLabels, + q_score: qScoreLabels, + population: GNOMAD_POPULATION_NAMES, +} + +const legendLabels = (colorBy: ColorBy, keys: string[]) => + keys.map((key) => fixedLegendLabels[colorBy]?.[key] || key) + +const colorForValue = (colorBy: ColorBy | '', value: string) => + colorMap[colorBy]?.[value] || defaultColor + +const tickFormat = (n: number) => { if (n >= 1e9) { return `${(n / 1e9).toPrecision(3)}B` } @@ -40,23 +168,69 @@ const tickFormat = (n: any) => { const labelProps = { fontSize: 14, textAnchor: 'middle', +} as const + +type Range = { start: number; stop: number; label: string } + +type Props = { + maxRepeats: number + alleleSizeDistribution: AlleleSizeDistributionItem[] + colorBy: ColorBy | '' + repeatUnitLength: number | null + scaleType: ScaleType + ranges?: Range[] + size: { width: number } +} + +type Bin = Partial> & { + index: number + label: string + fullFrequency: number +} + +const legendKeys: Record = { + quality_description: [...genotypeQualityKeys], + q_score: [...qScoreKeys], + sex: ['XX', 'XY'], + population: ['nfe', 'afr', 'fin', 'amr', 'ami', 'asj', 'eas', 'mid', 'oth', 'sas'], +} + +const LegendFromColorBy = ({ colorBy }: { colorBy: ColorBy | '' }) => { + if (colorBy === '') { + return null + } + + const keys = legendKeys[colorBy] + const labels = legendLabels(colorBy, [...keys]) + const colors = keys.map((key) => colorMap[colorBy][key]) + const scale = scaleOrdinal().domain(labels).range(colors) + return ( + + ) +} + +const tooltipContent = (data: Bin, key: ColorByValue | ''): string => { + const repeatText = data.label === '1' ? '1 repeat' : data.label.toString() + ' repeats' + const alleles = data[key] || 0 + const alleleText = alleles === 1 ? '1 allele' : alleles.toString() + ' alleles' + return `${repeatText}: ${alleleText}` } const ShortTandemRepeatAlleleSizeDistributionPlot = withSize()( ({ - // @ts-expect-error TS(2339) FIXME: Property 'maxRepeats' does not exist on type '{}'. maxRepeats, - // @ts-expect-error TS(2339) FIXME: Property 'alleleSizeDistribution' does not exist o... Remove this comment to see the full error message alleleSizeDistribution, - // @ts-expect-error TS(2339) FIXME: Property 'repeatUnitLength' does not exist on type... Remove this comment to see the full error message + colorBy, repeatUnitLength, - // @ts-expect-error TS(2339) FIXME: Property 'size' does not exist on type '{}'. size: { width }, - // @ts-expect-error TS(2339) FIXME: Property 'scaleType' does not exist on type '{}'. - scaleType, - // @ts-expect-error TS(2339) FIXME: Property 'ranges' does not exist on type '{}'. - ranges, - }) => { + scaleType = 'linear', + ranges = [], + }: Props) => { const height = 300 const margin = { @@ -72,37 +246,64 @@ const ShortTandemRepeatAlleleSizeDistributionPlot = withSize()( const binSize = Math.max(1, Math.ceil(maxRepeats / (plotWidth / 10))) const nBins = Math.floor(maxRepeats / binSize) + 1 - const data = useMemo(() => { - const d = Array.from(Array(nBins).keys()).map((n: any) => ({ - binIndex: n, - label: binSize === 1 ? `${n}` : `${n * binSize} - ${n * binSize + binSize - 1}`, - count: 0, - })) + const binLabels: string[] = [...Array(nBins).keys()].map((binIndex) => + binSize === 1 ? `${binIndex}` : `${binIndex * binSize} - ${binIndex * binSize + binSize - 1}` + ) - // @ts-expect-error TS(7031) FIXME: Binding element 'repeatCount' implicitly has an 'a... Remove this comment to see the full error message - alleleSizeDistribution.forEach(([repeatCount, nAlleles]) => { - const binIndex = Math.floor(repeatCount / binSize) - d[binIndex].count += nAlleles - }) + const emptyBins: Bin[] = Array.from(Array(nBins)).map((_, binIndex) => ({ + label: binLabels[binIndex], + index: binIndex, + fullFrequency: 0, + })) - return d + const data: Bin[] = useMemo(() => { + const binsByColorByValue = alleleSizeDistribution.reduce((acc, item) => { + const binIndex = Math.floor(item.repunit_count / binSize) + const oldBin: Bin = acc[binIndex] + const oldFrequency = oldBin[item.colorByValue] || 0 + const newFrequency = oldFrequency + item.frequency + const newBin: Bin = { + ...oldBin, + [item.colorByValue]: newFrequency, + fullFrequency: oldBin.fullFrequency + item.frequency, + } + return { ...acc, [binIndex]: newBin } + }, emptyBins) + return Object.values(binsByColorByValue) }, [alleleSizeDistribution, nBins, binSize]) - const xScale = scaleBand() - .domain(data.map((d: any) => d.binIndex)) + const keys = useMemo(() => { + const keySet: Record = data + .flatMap((bin) => Object.keys(bin)) + .reduce((acc, key) => ({ ...acc, [key]: true }), {}) + return Object.keys(keySet).filter( + (key) => key !== 'index' && key !== 'label' && key !== 'fullFrequency' + ) + }, [data]) + // maps binIndex and colorByValue to a y and y start + + const xScale = scaleBand() + .domain(data.map((d) => d.index)) .range([0, plotWidth]) const xBandwidth = xScale.bandwidth() - let yScale: any + let yScale: AnyD3Scale if (scaleType === 'log') { - const maxLog = Math.ceil(Math.log10(max(data, (d: any) => d.count) || 1)) + const maxLog = Math.ceil(Math.log10(max(data, (d) => d.fullFrequency) || 1)) yScale = scaleLog() .domain([1, 10 ** maxLog]) - .range([plotHeight - 10, 0]) + .range([plotHeight, 0]) + .clamp(true) + } else if (scaleType === 'linear-truncated-50') { + yScale = scaleLinear().domain([0, 50]).range([plotHeight, 0]).clamp(true) + } else if (scaleType === 'linear-truncated-200') { + yScale = scaleLinear().domain([0, 200]).range([plotHeight, 0]).clamp(true) + } else if (scaleType === 'linear-truncated-1000') { + yScale = scaleLinear().domain([0, 1000]).range([plotHeight, 0]).clamp(true) } else { yScale = scaleLinear() - .domain([0, max(data, (d: any) => d.count) || 1]) + .domain([0, max(data, (d) => d.fullFrequency) || 1]) .range([plotHeight, 0]) } @@ -117,8 +318,7 @@ const ShortTandemRepeatAlleleSizeDistributionPlot = withSize()( const readLengthBinIndex = Math.floor(readLengthInRepeats / binSize) // Read length line should be drawn at the center of the range for its value. readLengthX = - // @ts-expect-error TS(2532) FIXME: Object is possibly 'undefined'. - xScale(readLengthBinIndex) + + (xScale(readLengthBinIndex) || 0) + ((readLengthInRepeats - readLengthBinIndex * binSize) / binSize) * xBandwidth + xBandwidth / binSize / 2 } @@ -126,18 +326,17 @@ const ShortTandemRepeatAlleleSizeDistributionPlot = withSize()( return ( + - // @ts-expect-error TS(7015) FIXME: Element implicitly has an 'any' type because index... Remove this comment to see the full error message - (binIndex as any) % labelInterval === 0 ? data[binIndex].label : '' + tickFormat={(binIndex: number) => + binIndex % labelInterval === 0 ? binLabels[binIndex] : '' } tickLabelProps={ binSize === 1 @@ -157,8 +356,7 @@ const ShortTandemRepeatAlleleSizeDistributionPlot = withSize()( fontSize: 10, textAnchor: 'end', transform: `translate(0, 0), rotate(-40 ${ - // @ts-expect-error TS(2532) FIXME: Object is possibly 'undefined'. - xScale(binIndex) + xBandwidth / 2 + (xScale(binIndex) || 0) + xBandwidth / 2 }, 0)`, } } @@ -168,7 +366,6 @@ const ShortTandemRepeatAlleleSizeDistributionPlot = withSize()( (Number.isInteger(Math.log10(n)) ? tickFormat(n) : '') - : tickFormat + ? (n: unknown) => + Number.isInteger(Math.log10(n as number)) ? tickFormat(n as number) : '' + : (n: unknown) => tickFormat(n as number) } tickLabelProps={() => ({ dx: '-0.25em', @@ -200,54 +397,53 @@ const ShortTandemRepeatAlleleSizeDistributionPlot = withSize()( /> )} - {data.map((d: any) => { - const y = d.count === 0 ? plotHeight : yScale(d.count) - return ( - - - - - - - ) - })} + colorForValue(colorBy, key.toString())} + x={(bin) => bin.index} + y0={(point) => point[0] || 0} + y1={(point) => point[1] || 0} + > + {(stacks) => + stacks.map((stack) => + stack.bars.map((bar) => { + const tooltip = tooltipContent(bar.bar.data, bar.key as ColorByValue | '') + return ( + + + + + + + + ) + }) + ) + } + {ranges - .filter((range: any) => range.start !== range.stop) - .filter((range: any) => range.start <= maxRepeats) - .map((range: any, rangeIndex: any) => { + .filter((range) => range.start !== range.stop) + .filter((range) => range.start <= maxRepeats) + .map((range, rangeIndex) => { const startBinIndex = Math.floor(range.start / binSize) const startX = - // @ts-expect-error TS(2532) FIXME: Object is possibly 'undefined'. - xScale(startBinIndex) + + (xScale(startBinIndex) || 0) + ((range.start - startBinIndex * binSize) / binSize) * xBandwidth let stopX if (range.stop <= maxRepeats) { const stopBinIndex = Math.floor(range.stop / binSize) stopX = - // @ts-expect-error TS(2532) FIXME: Object is possibly 'undefined'. - xScale(stopBinIndex) + + (xScale(stopBinIndex) || 0) + ((range.stop - stopBinIndex * binSize) / binSize) * xBandwidth } else { stopX = plotWidth @@ -350,25 +546,4 @@ const ShortTandemRepeatAlleleSizeDistributionPlot = withSize()( ShortTandemRepeatAlleleSizeDistributionPlot.displayName = 'ShortTandemRepeatAlleleSizeDistributionPlot' -ShortTandemRepeatAlleleSizeDistributionPlot.propTypes = { - // @ts-expect-error TS(2322) FIXME: Type '{ maxRepeats: PropTypes.Validator; a... Remove this comment to see the full error message - maxRepeats: PropTypes.number.isRequired, - alleleSizeDistribution: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.number)).isRequired, - repeatUnitLength: PropTypes.number, - scaleType: PropTypes.oneOf(['linear', 'log']), - ranges: PropTypes.arrayOf( - PropTypes.shape({ - start: PropTypes.number.isRequired, - stop: PropTypes.number.isRequired, - label: PropTypes.string.isRequired, - }) - ), -} - -ShortTandemRepeatAlleleSizeDistributionPlot.defaultProps = { - // @ts-expect-error TS(2322) FIXME: Type '{ scaleType: string; ranges: never[]; }' is ... Remove this comment to see the full error message - scaleType: 'linear', - ranges: [], -} - export default ShortTandemRepeatAlleleSizeDistributionPlot diff --git a/browser/src/ShortTandemRepeatPage/ShortTandemRepeatAttributes.tsx b/browser/src/ShortTandemRepeatPage/ShortTandemRepeatAttributes.tsx index ec8457f4f..764772677 100644 --- a/browser/src/ShortTandemRepeatPage/ShortTandemRepeatAttributes.tsx +++ b/browser/src/ShortTandemRepeatPage/ShortTandemRepeatAttributes.tsx @@ -11,14 +11,11 @@ type ShortTandemRepeatRepeatUnitsProps = { } const ShortTandemRepeatRepeatUnits = ({ shortTandemRepeat }: ShortTandemRepeatRepeatUnitsProps) => { - const repeatUnitsByClassification = {} + const repeatUnitsByClassification: Record = {} shortTandemRepeat.repeat_units.forEach((repeatUnit) => { - // @ts-expect-error TS(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message if (repeatUnitsByClassification[repeatUnit.classification] === undefined) { - // @ts-expect-error TS(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message repeatUnitsByClassification[repeatUnit.classification] = [] } - // @ts-expect-error TS(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message repeatUnitsByClassification[repeatUnit.classification].push(repeatUnit.repeat_unit) }) @@ -31,7 +28,7 @@ const ShortTandemRepeatRepeatUnits = ({ shortTandemRepeat }: ShortTandemRepeatRe label={`Repeat unit${(repeatUnitsByClassification as any).unknown.length > 1 ? 's' : ''}`} > ( + items={(repeatUnitsByClassification as any).unknown.map((repeatUnit: string) => ( {repeatUnit === shortTandemRepeat.reference_repeat_unit && shortTandemRepeat.repeat_units.length > 1 @@ -45,76 +42,109 @@ const ShortTandemRepeatRepeatUnits = ({ shortTandemRepeat }: ShortTandemRepeatRe ) } - return ( - <> - {(repeatUnitsByClassification as any).pathogenic && ( - 1 ? 's' : '' - }`} - tooltip="These repeat units have been reported in the literature as pathogenic when they expand beyond a certain threshold." - > - ( - - {repeatUnit === shortTandemRepeat.reference_repeat_unit && - shortTandemRepeat.repeat_units.length > 1 - ? `${repeatUnit} (reference)` - : repeatUnit} - - ))} + if ( + (repeatUnitsByClassification as any).pathogenic && + (repeatUnitsByClassification as any).pathogenic.length == 1 && + !(repeatUnitsByClassification as any).benign && + !(repeatUnitsByClassification as any).unknown + ) { + return ( + <> + {(repeatUnitsByClassification as any).pathogenic && ( + 1 ? 's' : '' + }`} + > + ( + + {repeatUnit === shortTandemRepeat.reference_repeat_unit && + shortTandemRepeat.repeat_units.length > 1 + ? `${repeatUnit} (reference)` + : repeatUnit} + + ))} + label={`Pathogenic repeat unit${ + (repeatUnitsByClassification as any).pathogenic.length > 1 ? 's' : '' + }`} + /> + + )} + + ) + } else { + return ( + <> + {(repeatUnitsByClassification as any).pathogenic && ( + 1 ? 's' : '' }`} - /> - - )} - {(repeatUnitsByClassification as any).benign && ( - 1 ? 's' : '' - }`} - tooltip="These repeat units are regarded in the literature as benign, even when expanded." - > - ( - - {repeatUnit === shortTandemRepeat.reference_repeat_unit && - shortTandemRepeat.repeat_units.length > 1 - ? `${repeatUnit} (reference)` - : repeatUnit} - - ))} + tooltip="These repeat units have been reported in the literature as pathogenic when they expand beyond a certain threshold." + > + ( + + {repeatUnit === shortTandemRepeat.reference_repeat_unit && + shortTandemRepeat.repeat_units.length > 1 + ? `${repeatUnit} (reference)` + : repeatUnit} + + ))} + label={`Pathogenic repeat unit${ + (repeatUnitsByClassification as any).pathogenic.length > 1 ? 's' : '' + }`} + /> + + )} + {(repeatUnitsByClassification as any).benign && ( + 1 ? 's' : '' }`} - /> - - )} - {(repeatUnitsByClassification as any).unknown && ( - 1 ? 's' : '' - }`} - tooltip="These are the other repeat units detected at this locus within gnomAD samples by the call_non_ref_pathogenic_motifs.py script." - > - ( - - {repeatUnit === shortTandemRepeat.reference_repeat_unit && - shortTandemRepeat.repeat_units.length > 1 - ? `${repeatUnit} (reference)` - : repeatUnit} - - ))} + tooltip="These repeat units are regarded in the literature as benign, even when expanded." + > + ( + + {repeatUnit === shortTandemRepeat.reference_repeat_unit && + shortTandemRepeat.repeat_units.length > 1 + ? `${repeatUnit} (reference)` + : repeatUnit} + + ))} + label={`Benign repeat unit${ + (repeatUnitsByClassification as any).benign.length > 1 ? 's' : '' + }`} + /> + + )} + {(repeatUnitsByClassification as any).unknown && ( + 1 ? 's' : '' }`} - /> - - )} - - ) + tooltip="These are the other repeat units detected at this locus within gnomAD samples by the call_non_ref_pathogenic_motifs.py script." + > + ( + + {repeatUnit === shortTandemRepeat.reference_repeat_unit && + shortTandemRepeat.repeat_units.length > 1 + ? `${repeatUnit} (reference)` + : repeatUnit} + + ))} + label={`Other repeat unit${ + (repeatUnitsByClassification as any).unknown.length > 1 ? 's' : '' + }`} + /> + + )} + + ) + } } type ShortTandemRepeatAttributesProps = { @@ -132,10 +162,11 @@ const ShortTandemRepeatAttributes = ({ shortTandemRepeat }: ShortTandemRepeatAtt {shortTandemRepeat.gene.region} - {shortTandemRepeat.reference_region.chrom}-{shortTandemRepeat.reference_region.start}- - {shortTandemRepeat.reference_region.stop} + {shortTandemRepeat.main_reference_region.chrom}: + {shortTandemRepeat.main_reference_region.start}- + {shortTandemRepeat.main_reference_region.stop} diff --git a/browser/src/ShortTandemRepeatPage/ShortTandemRepeatColorBySelect.tsx b/browser/src/ShortTandemRepeatPage/ShortTandemRepeatColorBySelect.tsx new file mode 100644 index 000000000..6321188a0 --- /dev/null +++ b/browser/src/ShortTandemRepeatPage/ShortTandemRepeatColorBySelect.tsx @@ -0,0 +1,47 @@ +import React, { Dispatch, SetStateAction } from 'react' +import styled from 'styled-components' + +import { Select } from '@gnomad/ui' +import { ColorBy, ScaleType } from './ShortTandemRepeatAlleleSizeDistributionPlot' + +const Label = styled.label` + padding-right: 1em; +` + +type Props = { + id: string + selectedColorBy: ColorBy | '' + setSelectedColorBy: (newColorBy: ColorBy | '') => void + setSelectedScaleType: Dispatch> +} + +const ShortTandemRepeatColorBySelect = ({ + id, + selectedColorBy, + setSelectedColorBy, + setSelectedScaleType, +}: Props) => { + return ( + + ) +} + +export default ShortTandemRepeatColorBySelect diff --git a/browser/src/ShortTandemRepeatPage/ShortTandemRepeatGenotypeDistributionBinDetails.tsx b/browser/src/ShortTandemRepeatPage/ShortTandemRepeatGenotypeDistributionBinDetails.tsx index ac497fac3..a7a05a006 100644 --- a/browser/src/ShortTandemRepeatPage/ShortTandemRepeatGenotypeDistributionBinDetails.tsx +++ b/browser/src/ShortTandemRepeatPage/ShortTandemRepeatGenotypeDistributionBinDetails.tsx @@ -2,13 +2,22 @@ import React from 'react' import { List, ListItem } from '@gnomad/ui' -import { ShortTandemRepeat, ShortTandemRepeatAdjacentRepeat } from './ShortTandemRepeatPage' +import { + ShortTandemRepeat, + ShortTandemRepeatAdjacentRepeat, + GenotypeDistributionItem, +} from './ShortTandemRepeatPage' + import { getSelectedGenotypeDistribution } from './shortTandemRepeatHelpers' +import { Sex } from './ShortTandemRepeatAlleleSizeDistributionPlot' + type Props = { shortTandemRepeatOrAdjacentRepeat: ShortTandemRepeat | ShortTandemRepeatAdjacentRepeat - selectedPopulationId: string - selectedRepeatUnits: string + selectedPopulation: string | '' + selectedSex: Sex | '' + selectedRepeatUnits: string[] | '' + repeatUnitPairs: string[][] bin: { label: string xRange: number[] @@ -18,71 +27,88 @@ type Props = { const ShortTandemRepeatGenotypeDistributionBinDetails = ({ shortTandemRepeatOrAdjacentRepeat, - selectedPopulationId, + selectedPopulation, + selectedSex, selectedRepeatUnits, + repeatUnitPairs, bin, }: Props) => { const genotypeDistribution = getSelectedGenotypeDistribution(shortTandemRepeatOrAdjacentRepeat, { - selectedPopulationId, + selectedPopulation, selectedRepeatUnits, + selectedSex, }) - const isInBin = (d: any) => - bin.xRange[0] <= d[0] && d[0] <= bin.xRange[1] && bin.yRange[0] <= d[1] && d[1] <= bin.yRange[1] + const isInBin = (item: GenotypeDistributionItem) => + bin.xRange[0] <= item.long_allele_repunit_count && + item.long_allele_repunit_count <= bin.xRange[1] && + bin.yRange[0] <= item.short_allele_repunit_count && + item.short_allele_repunit_count <= bin.yRange[1] return ( <> {/* @ts-expect-error TS(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */} - {/* @ts-expect-error TS(7031) FIXME: Binding element 'x' implicitly has an 'any' type. */} - {genotypeDistribution.filter(isInBin).map(([x, y, n]) => ( - // @ts-expect-error TS(2769) FIXME: No overload matches this call. - - {x} repeats / {y} repeats: {n} individuals - - ))} + {genotypeDistribution + .filter(isInBin) + .map(({ long_allele_repunit_count, short_allele_repunit_count, frequency }) => ( + // @ts-expect-error TS(2769) FIXME: No overload matches this call. + + {long_allele_repunit_count} repeats / {short_allele_repunit_count} repeats:{' '} + {frequency} individuals + + ))} {!selectedRepeatUnits && ( <>

Repeat Units

{/* @ts-expect-error TS(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */} - {shortTandemRepeatOrAdjacentRepeat.genotype_distribution.repeat_units - .map((repeatUnitsDistribution) => repeatUnitsDistribution.repeat_units) + {repeatUnitPairs .map((repeatUnits) => ({ repeatUnits, distribution: getSelectedGenotypeDistribution(shortTandemRepeatOrAdjacentRepeat, { - selectedPopulationId, - selectedRepeatUnits: repeatUnits.join(' / '), + selectedPopulation, + selectedSex, + selectedRepeatUnits: repeatUnits, }), })) - .flatMap(({ repeatUnits, distribution }: any) => [ + .flatMap(({ repeatUnits, distribution }) => [ { repeatUnits, - distribution: distribution.filter((d: any) => d[0] >= d[1]).filter(isInBin), + distribution: distribution + .filter((d) => d.long_allele_repunit_count >= d.short_allele_repunit_count) + .filter(isInBin), }, { repeatUnits: [...repeatUnits].reverse(), distribution: distribution - .filter((d: any) => d[0] < d[1]) - .map((d: any) => [d[1], d[0], d[2]]) + .filter((d) => d.long_allele_repunit_count < d.short_allele_repunit_count) + .map((d) => ({ + ...d, + long_allele_repunit_count: d.short_allele_repunit_count, + short_allele_repunit_count: d.long_allele_repunit_count, + })) .filter(isInBin), }, ]) - .filter(({ distribution }: any) => distribution.length > 0) - .map(({ repeatUnits, distribution }: any) => ( + .map(({ repeatUnits, distribution }) => ( // @ts-expect-error TS(2769) FIXME: No overload matches this call. {repeatUnits.join(' / ')} {/* @ts-expect-error TS(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */} - {/* @ts-expect-error TS(7031) FIXME: Binding element 'x' implicitly has an 'any' type. */} - {distribution.map(([x, y, n]) => ( - // @ts-expect-error TS(2769) FIXME: No overload matches this call. - - {x} repeats / {y} repeats: {n} individuals - - ))} + {distribution.map( + ({ short_allele_repunit_count, long_allele_repunit_count, frequency }) => ( + // @ts-expect-error TS(2769) FIXME: No overload matches this call. + + {long_allele_repunit_count} repeats / {short_allele_repunit_count}{' '} + repeats: {frequency} individuals + + ) + )} ))} diff --git a/browser/src/ShortTandemRepeatPage/ShortTandemRepeatGenotypeDistributionPlot.tsx b/browser/src/ShortTandemRepeatPage/ShortTandemRepeatGenotypeDistributionPlot.tsx index 7ce7ef7d4..cf4269fd6 100644 --- a/browser/src/ShortTandemRepeatPage/ShortTandemRepeatGenotypeDistributionPlot.tsx +++ b/browser/src/ShortTandemRepeatPage/ShortTandemRepeatGenotypeDistributionPlot.tsx @@ -1,12 +1,12 @@ import { max } from 'd3-array' import { scaleBand, scaleLog } from 'd3-scale' -import PropTypes from 'prop-types' import React from 'react' import { withSize } from 'react-sizeme' import styled from 'styled-components' import { AxisBottom, AxisLeft } from '@visx/axis' import { TooltipAnchor } from '@gnomad/ui' +import { GenotypeDistributionItem } from './ShortTandemRepeatPage' // The 100% width/height container is necessary the component // to size to fit its container vs staying at its initial size. @@ -19,25 +19,39 @@ const GraphWrapper = styled.div` const labelProps = { fontSize: 14, textAnchor: 'middle', +} as const + +type PlotRange = { start: number; stop: number; label: string } + +type Props = { + axisLabels: string[] + maxRepeats: [number, number] + genotypeDistribution: GenotypeDistributionItem[] + xRanges: PlotRange[] + yRanges: PlotRange[] + onSelectBin: (bin: Bin) => void + size: { width: number } +} + +export type Bin = { + label: string + xBinIndex: number + yBinIndex: number + xRange: number[] + yRange: number[] + count: number } const ShortTandemRepeatGenotypeDistributionPlot = withSize()( ({ - // @ts-expect-error TS(2339) FIXME: Property 'axisLabels' does not exist on type '{}'. axisLabels, - // @ts-expect-error TS(2339) FIXME: Property 'maxRepeats' does not exist on type '{}'. maxRepeats, - // @ts-expect-error TS(2339) FIXME: Property 'genotypeDistribution' does not exist on ... Remove this comment to see the full error message genotypeDistribution, - // @ts-expect-error TS(2339) FIXME: Property 'size' does not exist on type '{}'. size: { width }, - // @ts-expect-error TS(2339) FIXME: Property 'xRanges' does not exist on type '{}'. - xRanges, - // @ts-expect-error TS(2339) FIXME: Property 'yRanges' does not exist on type '{}'. - yRanges, - // @ts-expect-error TS(2339) FIXME: Property 'onSelectBin' does not exist on type '{}'... Remove this comment to see the full error message - onSelectBin, - }) => { + xRanges = [], + yRanges = [], + onSelectBin = () => {}, + }: Props) => { const height = Math.min(width, 500) const margin = { @@ -56,7 +70,7 @@ const ShortTandemRepeatGenotypeDistributionPlot = withSize()( const yBinSize = Math.max(1, Math.ceil(maxRepeats[1] / (plotHeight / 10))) const yNumBins = Math.floor(maxRepeats[1] / yBinSize) + 1 - const data = Array.from(Array(xNumBins * yNumBins).keys()).map((n: any) => { + const data = Array.from(Array(xNumBins * yNumBins).keys()).map((n) => { const xBinIndex = Math.floor(n / yNumBins) const yBinIndex = n % yNumBins @@ -78,7 +92,7 @@ const ShortTandemRepeatGenotypeDistributionPlot = withSize()( ? `${yBinIndex}` : `${yBinIndex * yBinSize} - ${yBinIndex * yBinSize + yBinSize - 1}` - return { + const result: Bin = { label: `${xLabel} repeats in ${axisLabels[0]} / ${yLabel} repeats in ${axisLabels[1]}`, xBinIndex, yBinIndex, @@ -86,23 +100,23 @@ const ShortTandemRepeatGenotypeDistributionPlot = withSize()( yRange, count: 0, } + return result }) - // @ts-expect-error TS(7031) FIXME: Binding element 'repeats1' implicitly has an 'any'... Remove this comment to see the full error message - genotypeDistribution.forEach(([repeats1, repeats2, nAlleles]) => { - const xBinIndex = Math.floor(repeats1 / xBinSize) - const yBinIndex = Math.floor(repeats2 / yBinSize) - data[xBinIndex * yNumBins + yBinIndex].count += nAlleles - }) + genotypeDistribution.forEach( + ({ short_allele_repunit_count, long_allele_repunit_count, frequency }) => { + const xBinIndex = Math.floor(short_allele_repunit_count / xBinSize) + const yBinIndex = Math.floor(long_allele_repunit_count / yBinSize) + data[xBinIndex * yNumBins + yBinIndex].count += frequency + } + ) - const xScale = scaleBand() - // @ts-expect-error TS(2345) FIXME: Argument of type 'number[]' is not assignable to p... Remove this comment to see the full error message + const xScale = scaleBand() .domain(Array.from(Array(xNumBins).keys())) .range([0, plotWidth]) const xBandwidth = xScale.bandwidth() - const yScale = scaleBand() - // @ts-expect-error TS(2345) FIXME: Argument of type 'number[]' is not assignable to p... Remove this comment to see the full error message + const yScale = scaleBand() .domain(Array.from(Array(yNumBins).keys())) .range([plotHeight, 0]) const yBandwidth = yScale.bandwidth() @@ -110,7 +124,7 @@ const ShortTandemRepeatGenotypeDistributionPlot = withSize()( const xMaxNumLabels = Math.floor(plotWidth / 20) const xLabelInterval = Math.max(Math.round(xNumBins / xMaxNumLabels), 1) - const xTickFormat = (binIndex: any) => { + const xTickFormat = (binIndex: number) => { if (binIndex % xLabelInterval !== 0) { return '' } @@ -122,7 +136,7 @@ const ShortTandemRepeatGenotypeDistributionPlot = withSize()( return `${binIndex * xBinSize} - ${binIndex * xBinSize + xBinSize - 1}` } - const yTickFormat = (binIndex: any) => { + const yTickFormat = (binIndex: number) => { if (yBinSize === 1) { return `${binIndex}` } @@ -131,8 +145,7 @@ const ShortTandemRepeatGenotypeDistributionPlot = withSize()( } const opacityScale = scaleLog() - // @ts-expect-error TS(2345) FIXME: Argument of type '(string | number | undefined)[]'... Remove this comment to see the full error message - .domain([1, max(genotypeDistribution, (d: any) => d[2])]) + .domain([1, max(genotypeDistribution, (d) => d.frequency) || 2]) .range([0.1, 1]) return ( @@ -141,7 +154,6 @@ const ShortTandemRepeatGenotypeDistributionPlot = withSize()( {data - .filter((d: any) => d.count !== 0) - .map((d: any) => { + .filter((d) => d.count !== 0) + .map((d) => { return ( {xRanges - .filter((range: any) => range.start !== range.stop) - .filter((range: any) => range.start <= maxRepeats[0]) - .map((range: any, rangeIndex: any, ranges: any) => { + .filter((range) => range.start !== range.stop) + .filter((range) => range.start <= maxRepeats[0]) + .map((range, rangeIndex, ranges) => { const startBinIndex = Math.floor(range.start / xBinSize) const startX = - // @ts-expect-error TS(2532) FIXME: Object is possibly 'undefined'. - xScale(startBinIndex) + + (xScale(startBinIndex) || 0) + ((range.start - startBinIndex * xBinSize) / xBinSize) * xBandwidth let stopX if (range.stop <= maxRepeats[0]) { const stopBinIndex = Math.floor(range.stop / xBinSize) stopX = - // @ts-expect-error TS(2532) FIXME: Object is possibly 'undefined'. - xScale(stopBinIndex) + + (xScale(stopBinIndex) || 0) + ((range.stop - stopBinIndex * xBinSize) / xBinSize) * xBandwidth } else { stopX = plotWidth @@ -319,21 +327,19 @@ const ShortTandemRepeatGenotypeDistributionPlot = withSize()( {yRanges - .filter((range: any) => range.start !== range.stop) - .filter((range: any) => range.start <= maxRepeats[1]) - .map((range: any, rangeIndex: any, ranges: any) => { + .filter((range) => range.start !== range.stop) + .filter((range) => range.start <= maxRepeats[1]) + .map((range, rangeIndex, ranges) => { const startBinIndex = Math.floor(range.start / yBinSize) const startY = - // @ts-expect-error TS(2532) FIXME: Object is possibly 'undefined'. - yScale(startBinIndex) + + (yScale(startBinIndex) || 0) + (1 - (range.start - startBinIndex * yBinSize) / yBinSize) * yBandwidth let stopY if (range.stop <= maxRepeats[1]) { const stopBinIndex = Math.floor(range.stop / yBinSize) stopY = - // @ts-expect-error TS(2532) FIXME: Object is possibly 'undefined'. - yScale(stopBinIndex) + + (yScale(stopBinIndex) || 0) + (1 - (range.stop - stopBinIndex * yBinSize) / yBinSize) * yBandwidth } else { stopY = 0 @@ -403,30 +409,7 @@ const ShortTandemRepeatGenotypeDistributionPlot = withSize()( ShortTandemRepeatGenotypeDistributionPlot.displayName = 'ShortTandemRepeatGenotypeDistributionPlot' -ShortTandemRepeatGenotypeDistributionPlot.propTypes = { - // @ts-expect-error TS(2322) FIXME: Type '{ axisLabels: PropTypes.Validator<(string | ... Remove this comment to see the full error message - axisLabels: PropTypes.arrayOf(PropTypes.string).isRequired, - maxRepeats: PropTypes.arrayOf(PropTypes.number).isRequired, - genotypeDistribution: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.number)).isRequired, - xRanges: PropTypes.arrayOf( - PropTypes.shape({ - start: PropTypes.number.isRequired, - stop: PropTypes.number.isRequired, - label: PropTypes.string.isRequired, - }) - ), - yRanges: PropTypes.arrayOf( - PropTypes.shape({ - start: PropTypes.number.isRequired, - stop: PropTypes.number.isRequired, - label: PropTypes.string.isRequired, - }) - ), - onSelectBin: PropTypes.func, -} - ShortTandemRepeatGenotypeDistributionPlot.defaultProps = { - // @ts-expect-error TS(2322) FIXME: Type '{ xRanges: never[]; yRanges: never[]; onSele... Remove this comment to see the full error message xRanges: [], yRanges: [], onSelectBin: () => {}, diff --git a/browser/src/ShortTandemRepeatPage/ShortTandemRepeatGenotypeDistributionRepeatUnitsSelect.tsx b/browser/src/ShortTandemRepeatPage/ShortTandemRepeatGenotypeDistributionRepeatUnitsSelect.tsx index 381f96e90..55e4f1294 100644 --- a/browser/src/ShortTandemRepeatPage/ShortTandemRepeatGenotypeDistributionRepeatUnitsSelect.tsx +++ b/browser/src/ShortTandemRepeatPage/ShortTandemRepeatGenotypeDistributionRepeatUnitsSelect.tsx @@ -1,37 +1,35 @@ -import React from 'react' +import React, { Dispatch, SetStateAction } from 'react' import { Select } from '@gnomad/ui' +import { ShortTandemRepeat, ShortTandemRepeatAdjacentRepeat } from './ShortTandemRepeatPage' +import { genotypeRepunitPairs, isAdjacentRepeat } from './shortTandemRepeatHelpers' type Props = { - shortTandemRepeatOrAdjacentRepeat: { - id: string - associated_diseases?: any[] - reference_repeat_unit: string - genotype_distribution: { - repeat_units: { - repeat_units?: string[] - }[] - } - repeat_units: any[] - } - value: string - onChange: (...args: any[]) => any + shortTandemRepeatOrAdjacentRepeat: ShortTandemRepeat | ShortTandemRepeatAdjacentRepeat + selectedRepeatUnits: string[] | '' + setSelectedRepeatUnits: Dispatch> } const ShortTandemRepeatGenotypeDistributionRepeatUnitsSelect = ({ shortTandemRepeatOrAdjacentRepeat, - value, - onChange, + selectedRepeatUnits, + setSelectedRepeatUnits, }: Props) => { // Adjacent repeats do not have classifications for repeat units. - const isAdjacentRepeat = !shortTandemRepeatOrAdjacentRepeat.associated_diseases - const repeatUnitClassifications = isAdjacentRepeat + const repeatUnitClassifications: Record = isAdjacentRepeat( + shortTandemRepeatOrAdjacentRepeat + ) ? {} : shortTandemRepeatOrAdjacentRepeat.repeat_units.reduce( (acc, repeatUnit) => ({ ...acc, [repeatUnit.repeat_unit]: repeatUnit.classification }), {} ) + const repunitPairs = genotypeRepunitPairs(shortTandemRepeatOrAdjacentRepeat) + + if (repunitPairs.length == 1) { + return null + } return ( diff --git a/browser/src/ShortTandemRepeatPage/ShortTandemRepeatPage.tsx b/browser/src/ShortTandemRepeatPage/ShortTandemRepeatPage.tsx index 833e19460..3d2c1f6ec 100644 --- a/browser/src/ShortTandemRepeatPage/ShortTandemRepeatPage.tsx +++ b/browser/src/ShortTandemRepeatPage/ShortTandemRepeatPage.tsx @@ -1,5 +1,4 @@ -import { max, min } from 'd3-array' -import React, { useState } from 'react' +import React, { SetStateAction, useState, Dispatch } from 'react' import styled from 'styled-components' import { Badge, Button, ExternalLink, List, ListItem, Modal, Select } from '@gnomad/ui' @@ -14,8 +13,18 @@ import ShortTandemRepeatAgeDistributionPlot from './ShortTandemRepeatAgeDistribu import ShortTandemRepeatAssociatedDiseasesTable from './ShortTandemRepeatAssociatedDiseasesTable' import ShortTandemRepeatAttributes from './ShortTandemRepeatAttributes' import ShortTandemRepeatPopulationOptions from './ShortTandemRepeatPopulationOptions' -import ShortTandemRepeatAlleleSizeDistributionPlot from './ShortTandemRepeatAlleleSizeDistributionPlot' -import ShortTandemRepeatGenotypeDistributionPlot from './ShortTandemRepeatGenotypeDistributionPlot' +import ShortTandemRepeatColorBySelect from './ShortTandemRepeatColorBySelect' +import ShortTandemRepeatAlleleSizeDistributionPlot, { + ColorBy, + GenotypeQuality, + QScoreBin, + Sex, + ScaleType, + AlleleSizeDistributionItem, +} from './ShortTandemRepeatAlleleSizeDistributionPlot' +import ShortTandemRepeatGenotypeDistributionPlot, { + Bin as GenotypeBin, +} from './ShortTandemRepeatGenotypeDistributionPlot' import ShortTandemRepeatGenotypeDistributionBinDetails from './ShortTandemRepeatGenotypeDistributionBinDetails' import ShortTandemRepeatGenotypeDistributionRepeatUnitsSelect from './ShortTandemRepeatGenotypeDistributionRepeatUnitsSelect' import ShortTandemRepeatReads from './ShortTandemRepeatReads' @@ -23,50 +32,62 @@ import { getSelectedAlleleSizeDistribution, getSelectedGenotypeDistribution, getGenotypeDistributionPlotAxisLabels, + maxAlleleSizeDistributionRepeats, + maxGenotypeDistributionRepeats, + genotypeRepunitPairs, } from './shortTandemRepeatHelpers' import ShortTandemRepeatAdjacentRepeatSection from './ShortTandemRepeatAdjacentRepeatSection' +import { PopulationId } from '@gnomad/dataset-metadata/gnomadPopulations' -type ShortTandemRepeatRepeatUnit = { - repeat_unit: string +type ShortTandemRepeatReferenceRegion = { + chrom: string + start: number + stop: number +} + +export type AlleleSizeDistributionCohort = { + ancestry_group: PopulationId + sex: Sex + repunit: string + quality_description: GenotypeQuality + q_score: QScoreBin + distribution: AlleleSizeDistributionItem[] +} + +export type GenotypeDistributionItem = { + short_allele_repunit_count: number + long_allele_repunit_count: number + frequency: number +} + +export type GenotypeDistributionCohort = { + ancestry_group: string + sex: Sex + short_allele_repunit: string + long_allele_repunit: string + quality_description: GenotypeQuality + q_score: QScoreBin + distribution: GenotypeDistributionItem[] +} + +export type AgeDistributionItem = { + age_range: [number | null, number | null] distribution: number[][] - populations: { - id: string - distribution: number[][] - }[] } export type ShortTandemRepeatAdjacentRepeat = { id: string - reference_region: { - chrom: string - start: number - stop: number - } + reference_region: ShortTandemRepeatReferenceRegion reference_repeat_unit: string repeat_units: string[] - allele_size_distribution: { - distribution: number[][] - populations: { - id: string - distribution: number[][] - }[] - repeat_units: ShortTandemRepeatRepeatUnit[] - } - genotype_distribution: { - distribution: number[][] - populations: { - id: string - distribution: number[][] - }[] - repeat_units: { - repeat_units: string[] - distribution: number[][] - populations: { - id: string - distribution: number[][] - }[] - }[] - } + allele_size_distribution: AlleleSizeDistributionCohort[] + genotype_distribution: GenotypeDistributionCohort[] +} + +export type PlotRange = { + label: string + start: number + stop: number } export type ShortTandemRepeat = { @@ -89,39 +110,16 @@ export type ShortTandemRepeat = { notes: string | null }[] stripy_id: string | null - reference_region: { - chrom: string - start: number - stop: number - } + main_reference_region: ShortTandemRepeatReferenceRegion + reference_regions: ShortTandemRepeatReferenceRegion[] reference_repeat_unit: string repeat_units: { repeat_unit: string classification: string }[] - allele_size_distribution: { - distribution: number[][] - populations: { - id: string - distribution: number[][] - }[] - repeat_units: ShortTandemRepeatRepeatUnit[] - } - genotype_distribution: { - distribution: number[][] - populations: { - id: string - distribution: number[][] - }[] - repeat_units: { - repeat_units: string[] - distribution: number[][] - populations: { - id: string - distribution: number[][] - }[] - }[] - } + allele_size_distribution: AlleleSizeDistributionCohort[] + genotype_distribution: GenotypeDistributionCohort[] + age_distribution: AgeDistributionItem[] adjacent_repeats: ShortTandemRepeatAdjacentRepeat[] } @@ -140,50 +138,55 @@ const FlexWrapper = styled.div` width: 100%; ` -const parseCombinedPopulationId = (combinedPopulationId: any) => { - let population - let sex - if (combinedPopulationId.includes('_')) { - ;[population, sex] = combinedPopulationId.split('_') - } else if (combinedPopulationId === 'XX' || combinedPopulationId === 'XY') { - population = null - sex = combinedPopulationId - } else { - population = combinedPopulationId - sex = null - } - return { population, sex } -} - type ShortTandemRepeatPageProps = { datasetId: DatasetId shortTandemRepeat: ShortTandemRepeat } +// Stacked bar plots only make sense when the y scale factor stays constant +// throughout, so log scale is only allowed when there's only one bar per +// column, that is, when not breaking down the data into subsets. +const logScaleAllowed = (colorBy: ColorBy | '') => colorBy === '' + const ShortTandemRepeatPage = ({ datasetId, shortTandemRepeat }: ShortTandemRepeatPageProps) => { - const [selectedRepeatUnit, setSelectedRepeatUnit] = useState( - shortTandemRepeat.allele_size_distribution.repeat_units.length === 1 - ? shortTandemRepeat.allele_size_distribution.repeat_units[0].repeat_unit + const { allele_size_distribution } = shortTandemRepeat + + const alleleSizeDistributionRepunits = [ + ...new Set(allele_size_distribution.map((cohort) => cohort.repunit)), + ].sort() + const genotypeDistributionRepunitPairs = genotypeRepunitPairs(shortTandemRepeat) + + const defaultAlleleSizeRepunit = + alleleSizeDistributionRepunits.length === 1 ? alleleSizeDistributionRepunits[0] : '' + const defaultGenotypeDistributionRepunits = + genotypeDistributionRepunitPairs.length === 1 ? genotypeDistributionRepunitPairs[0] : '' + const defaultDisease = + shortTandemRepeat.associated_diseases.length > 0 + ? shortTandemRepeat.associated_diseases[0].name : '' - ) - - const [selectedPopulationId, setSelectedPopulationId] = useState('') - const [selectedScaleType, setSelectedScaleType] = useState('linear') - const [selectedGenotypeDistributionRepeatUnits, setSelectedGenotypeDistributionRepeatUnits] = - useState( - shortTandemRepeat.genotype_distribution.repeat_units.length === 1 - ? shortTandemRepeat.genotype_distribution.repeat_units[0].repeat_units.join(' / ') - : '' - ) + const [selectedPopulation, setSelectedPopulation] = useState('') + const [selectedSex, setSelectedSex] = useState('') + const [selectedScaleType, setSelectedScaleType] = useState('linear') + const [selectedColorBy, rawSetSelectedColorBy] = useState('') - const [selectedDisease, setSelectedDisease] = useState( - shortTandemRepeat.associated_diseases[0].name - ) + const setSelectedColorBy = (newColorBy: ColorBy | '') => { + if (selectedScaleType === 'log' && !logScaleAllowed(newColorBy)) { + setSelectedScaleType('linear') + } + rawSetSelectedColorBy(newColorBy) + } - const [showAdjacentRepeats, setShowAdjacentRepeats] = useState(false) + const [selectedAlleleSizeRepeatUnit, setSelectedAlleleSizeRepeatUnit] = + useState(defaultAlleleSizeRepunit) + const [selectedGenotypeDistributionRepeatUnits, setSelectedGenotypeDistributionRepeatUnits] = + useState(defaultGenotypeDistributionRepunits) + const [selectedDisease, setSelectedDisease] = useState(defaultDisease) + const [showAdjacentRepeats, setShowAdjacentRepeats] = useState(false) - const populationIds = shortTandemRepeat.allele_size_distribution.populations.map((pop) => pop.id) + const populations = [ + ...new Set(shortTandemRepeat.allele_size_distribution.map((cohort) => cohort.ancestry_group)), + ].sort() const allRepeatUnitsByClassification: Record = {} shortTandemRepeat.repeat_units.forEach((repeatUnit) => { @@ -196,16 +199,14 @@ const ShortTandemRepeatPage = ({ datasetId, shortTandemRepeat }: ShortTandemRepe // This uses repeat units from shortTandemRepeat.allele_size_distribution.repeat_units because // shortTandemRepeat.repeat_units may include repeat units that do not appear in gnomAD. const repeatUnitsFoundInGnomad = new Set( - shortTandemRepeat.allele_size_distribution.repeat_units.map( - (repeatUnit) => repeatUnit.repeat_unit - ) + shortTandemRepeat.allele_size_distribution.map((cohort) => cohort.repunit) ) const repeatUnitsFoundInGnomadByClassification: Record = {} Object.keys(allRepeatUnitsByClassification).forEach((classification) => { repeatUnitsFoundInGnomadByClassification[classification] = allRepeatUnitsByClassification[ classification - ].filter((repeatUnit: any) => repeatUnitsFoundInGnomad.has(repeatUnit)) + ].filter((repeatUnit) => repeatUnitsFoundInGnomad.has(repeatUnit)) }) const allRepeatUnitsFoundInGnomadArePathogenic = Object.keys( @@ -224,7 +225,7 @@ const ShortTandemRepeatPage = ({ datasetId, shortTandemRepeat }: ShortTandemRepe ? diseaseToPlot.repeat_size_classifications : [] - const plotRanges = repeatSizeClassificationsToPlot.map((classification) => { + const plotRanges: PlotRange[] = repeatSizeClassificationsToPlot.map((classification) => { return { label: classification.classification, start: classification.min !== null ? classification.min : 0, @@ -232,7 +233,21 @@ const ShortTandemRepeatPage = ({ datasetId, shortTandemRepeat }: ShortTandemRepe } }) - const [selectedGenotypeDistributionBin, setSelectedGenotypeDistributionBin] = useState(null) + const [selectedGenotypeDistributionBin, setSelectedGenotypeDistributionBin] = + useState(null) + + const maxAlleleRepeats = maxAlleleSizeDistributionRepeats(shortTandemRepeat) + + const isRepunitSelectionPathogenic = ( + selectedRepeatUnits: string[] | '', + allRepeatUnitsFoundInGnomadArePathogenic: boolean, + allRepeatUnitsByClassification: Record, + selectionIndex: number + ) => + (selectedRepeatUnits === '' && allRepeatUnitsFoundInGnomadArePathogenic) || + (allRepeatUnitsByClassification.pathogenic || []).includes( + selectedGenotypeDistributionRepeatUnits[selectionIndex] + ) return ( <> @@ -259,12 +274,21 @@ const ShortTandemRepeatPage = ({ datasetId, shortTandemRepeat }: ShortTandemRepe STRipy + {/* @ts-expect-error TS(2745) FIXME: This JSX tag's 'children' prop expects type 'never... Remove this comment to see the full error message */} + + {/* @ts-expect-error TS(2786) FIXME: 'ExternalLink' cannot be used as a JSX component. */} + + STRchive + + )} -

Related Loci

+

TRs in gnomAD

- Table of tandem repeat loci in gnomAD + Known disease-associated TRs

@@ -282,26 +306,25 @@ const ShortTandemRepeatPage = ({ datasetId, shortTandemRepeat }: ShortTandemRepe Allele Size Distribution + - + + + )} @@ -419,7 +441,7 @@ const ShortTandemRepeatPage = ({ datasetId, shortTandemRepeat }: ShortTandemRepe { + onChange={(e: { target: { value: string } }) => { setSelectedDisease(e.target.value) }} > @@ -534,11 +554,9 @@ const ShortTandemRepeatPage = ({ datasetId, shortTandemRepeat }: ShortTandemRepe {((selectedGenotypeDistributionRepeatUnits === '' && !allRepeatUnitsFoundInGnomadArePathogenic) || - !selectedGenotypeDistributionRepeatUnits - .split(' / ') - .every((repeatUnit) => - ((allRepeatUnitsByClassification as any).pathogenic || []).includes(repeatUnit) - )) && ( + !(selectedGenotypeDistributionRepeatUnits as string[]).every((repeatUnit) => + ((allRepeatUnitsByClassification as any).pathogenic || []).includes(repeatUnit) + )) && (

Note This plot includes non-pathogenic repeat units. Use the “Repeat units” menu to view specific repeat units. @@ -548,7 +566,7 @@ const ShortTandemRepeatPage = ({ datasetId, shortTandemRepeat }: ShortTandemRepe {selectedGenotypeDistributionBin && ( )} @@ -570,13 +590,8 @@ const ShortTandemRepeatPage = ({ datasetId, shortTandemRepeat }: ShortTandemRepe Age Distribution {!allRepeatUnitsFoundInGnomadArePathogenic && ( @@ -586,7 +601,7 @@ const ShortTandemRepeatPage = ({ datasetId, shortTandemRepeat }: ShortTandemRepe )}

- {shortTandemRepeat.adjacent_repeats.length > 0 && ( + {false && (

Adjacent Repeats @@ -597,11 +612,17 @@ const ShortTandemRepeatPage = ({ datasetId, shortTandemRepeat }: ShortTandemRepe ) }) @@ -623,7 +644,7 @@ const ShortTandemRepeatPage = ({ datasetId, shortTandemRepeat }: ShortTandemRepe Read Data{' '} 1 + alleleSizeDistributionRepunits.length > 1 ? 'str-read-data-multiple-repeat-units' : 'str-read-data' } @@ -631,18 +652,20 @@ const ShortTandemRepeatPage = ({ datasetId, shortTandemRepeat }: ShortTandemRepe

diff --git a/browser/src/ShortTandemRepeatPage/ShortTandemRepeatPageContainer.tsx b/browser/src/ShortTandemRepeatPage/ShortTandemRepeatPageContainer.tsx index ba8d0cd18..210a7bccf 100644 --- a/browser/src/ShortTandemRepeatPage/ShortTandemRepeatPageContainer.tsx +++ b/browser/src/ShortTandemRepeatPage/ShortTandemRepeatPageContainer.tsx @@ -31,7 +31,14 @@ query ${operationName}($strId: String!, $datasetId: DatasetId!) { } notes } - reference_region { + main_reference_region { + reference_genome + chrom + start + stop + } + reference_regions { + reference_genome chrom start stop @@ -42,33 +49,27 @@ query ${operationName}($strId: String!, $datasetId: DatasetId!) { classification } allele_size_distribution { - distribution - populations { - id - distribution - } - repeat_units { - repeat_unit - distribution - populations { - id - distribution - } + ancestry_group + sex + repunit + quality_description + q_score + distribution { + repunit_count + frequency } } genotype_distribution { - distribution - populations { - id - distribution - } - repeat_units { - repeat_units - distribution - populations { - id - distribution - } + ancestry_group + sex + short_allele_repunit + long_allele_repunit + quality_description + q_score + distribution { + short_allele_repunit_count + long_allele_repunit_count + frequency } } age_distribution { @@ -79,41 +80,36 @@ query ${operationName}($strId: String!, $datasetId: DatasetId!) { adjacent_repeats { id reference_region { + reference_genome chrom start stop } reference_repeat_unit repeat_units - allele_size_distribution { - distribution - populations { - id - distribution - } - repeat_units { - repeat_unit - distribution - populations { - id - distribution - } - } + } + allele_size_distribution { + ancestry_group + sex + repunit + quality_description + q_score + distribution { + repunit_count + frequency } - genotype_distribution { - distribution - populations { - id - distribution - } - repeat_units { - repeat_units - distribution - populations { - id - distribution - } - } + } + genotype_distribution { + ancestry_group + sex + short_allele_repunit + long_allele_repunit + quality_description + q_score + distribution { + short_allele_repunit_count + long_allele_repunit_count + frequency } } } diff --git a/browser/src/ShortTandemRepeatPage/ShortTandemRepeatPopulationOptions.tsx b/browser/src/ShortTandemRepeatPage/ShortTandemRepeatPopulationOptions.tsx index f217fc478..e232ad206 100644 --- a/browser/src/ShortTandemRepeatPage/ShortTandemRepeatPopulationOptions.tsx +++ b/browser/src/ShortTandemRepeatPage/ShortTandemRepeatPopulationOptions.tsx @@ -1,9 +1,11 @@ -import React from 'react' +import React, { Dispatch, SetStateAction } from 'react' import styled from 'styled-components' import { Select } from '@gnomad/ui' -import { GNOMAD_POPULATION_NAMES } from '@gnomad/dataset-metadata/gnomadPopulations' +import { PopulationId, GNOMAD_POPULATION_NAMES } from '@gnomad/dataset-metadata/gnomadPopulations' + +import { Sex } from './ShortTandemRepeatAlleleSizeDistributionPlot' const Wrapper = styled.div` @media (max-width: 600px) { @@ -17,74 +19,64 @@ const Wrapper = styled.div` } ` +const Label = styled.label` + padding-right: 1em; +` + type Props = { id: string - populationIds: string[] - selectedPopulationId: string - onSelectPopulationId: (...args: any[]) => any + populations: PopulationId[] + selectedPopulation: PopulationId | '' + selectedSex: Sex | '' + setSelectedPopulation: Dispatch> + setSelectedSex: Dispatch> } const ShortTandemRepeatPopulationOptions = ({ id, - populationIds, - selectedPopulationId, - onSelectPopulationId, + populations, + selectedPopulation, + selectedSex, + setSelectedPopulation, + setSelectedSex, }: Props) => { - const selectedAncestralPopulation = - selectedPopulationId === 'XX' || selectedPopulationId === 'XY' - ? '' - : selectedPopulationId.split('_')[0] - - let selectedSex = '' - if (selectedPopulationId.endsWith('XX')) { - selectedSex = 'XX' - } else if (selectedPopulationId.endsWith('XY')) { - selectedSex = 'XY' - } + const populationsSortedByName = populations.sort((group1, group2) => + GNOMAD_POPULATION_NAMES[group1].localeCompare(GNOMAD_POPULATION_NAMES[group2]) + ) return ( - ) } diff --git a/browser/src/ShortTandemRepeatPage/ShortTandemRepeatReads.tsx b/browser/src/ShortTandemRepeatPage/ShortTandemRepeatReads.tsx index 4cf2e151d..87bfa4a04 100644 --- a/browser/src/ShortTandemRepeatPage/ShortTandemRepeatReads.tsx +++ b/browser/src/ShortTandemRepeatPage/ShortTandemRepeatReads.tsx @@ -3,7 +3,7 @@ import styled from 'styled-components' import { Button, Input, Select } from '@gnomad/ui' -import { GNOMAD_POPULATION_NAMES } from '@gnomad/dataset-metadata/gnomadPopulations' +import { GNOMAD_POPULATION_NAMES, PopulationId } from '@gnomad/dataset-metadata/gnomadPopulations' import AttributeList, { AttributeListItem } from '../AttributeList' import Delayed from '../Delayed' @@ -43,7 +43,7 @@ type ShortTandemRepeatReadProps = { lower: number } }[] - population: string + population: PopulationId sex: string age?: string pcr_protocol: string @@ -58,7 +58,6 @@ const ShortTandemRepeatRead = ({ read }: ShortTandemRepeatReadProps) => {
- {/* @ts-expect-error TS(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message */} {GNOMAD_POPULATION_NAMES[read.population]} {read.sex} @@ -117,11 +116,10 @@ const ShortTandemRepeatReadContainer = ({ ) } - if (error) { + if (error || !read) { return Unable to load read } - // @ts-expect-error TS(2322) FIXME: Type 'null' is not assignable to type '{ alleles: ... Remove this comment to see the full error message return } @@ -194,15 +192,7 @@ const fetchReads = ({ datasetId, shortTandemRepeatId, filter, limit, offset }: a type ShortTandemRepeatReadsProps = { datasetId: string shortTandemRepeat: ShortTandemRepeat - filter: { - population?: string - sex?: string - alleles?: { - repeat_unit?: string - min_repeats?: number - max_repeat?: number - }[] - } + filter: Filters } const ShortTandemRepeatReads = ({ @@ -210,12 +200,12 @@ const ShortTandemRepeatReads = ({ shortTandemRepeat, filter, }: ShortTandemRepeatReadsProps) => { - const fetchReadsTimer = useRef(null) + const fetchReadsTimer = useRef | null>(null) const fetchNumReadsMemoized = useCallback(() => { - // @ts-expect-error TS(2769) FIXME: No overload matches this call. - clearTimeout(fetchReadsTimer.current) + if (fetchReadsTimer.current) { + clearTimeout(fetchReadsTimer.current) + } return new Promise((resolve: any, reject: any) => { - // @ts-expect-error TS(2322) FIXME: Type 'Timeout' is not assignable to type 'null'. fetchReadsTimer.current = setTimeout(() => { fetchNumReads({ datasetId, shortTandemRepeatId: shortTandemRepeat.id, filter }).then( resolve, @@ -224,8 +214,7 @@ const ShortTandemRepeatReads = ({ }, 300) }) }, [datasetId, shortTandemRepeat, filter]) - const { isLoading, response: numReads, error } = useRequest(fetchNumReadsMemoized) - + const { isLoading, response, error } = useRequest(fetchNumReadsMemoized) const readsStore = useRef(new Map()) const [readIndex, setReadIndex] = useState(0) @@ -283,6 +272,8 @@ const ShortTandemRepeatReads = ({ return Unable to load read data } + const numReads: number = response as unknown as number + if (numReads === 0) { return No matching samples found } @@ -307,18 +298,15 @@ const ShortTandemRepeatReads = ({ min={1} max={numReads} onChange={(e: any) => { - // @ts-expect-error TS(2531) FIXME: Object is possibly 'null'. - setReadIndex(Math.max(0, Math.min(numReads - 1, Number(e.target.value) - 1))) + setReadIndex(Math.max(0, Math.min(numReads! - 1, Number(e.target.value) - 1))) }} style={{ width: '10ch' }} />{' '} - {/* @ts-expect-error TS(2531) FIXME: Object is possibly 'null'. */} - of {numReads.toLocaleString()} + of {numReads!.toLocaleString()}