diff --git a/browser/src/GenePage/GenePage.tsx b/browser/src/GenePage/GenePage.tsx index fdab3431d..e686aaecc 100644 --- a/browser/src/GenePage/GenePage.tsx +++ b/browser/src/GenePage/GenePage.tsx @@ -22,6 +22,7 @@ import { isExac, hasCopyNumberVariants, isV2, + getTopLevelDataset, } from '@gnomad/dataset-metadata/metadata' import ConstraintTable from '../ConstraintTable/ConstraintTable' import VariantCooccurrenceCountsTable, { @@ -50,7 +51,7 @@ import MitochondrialGeneCoverageTrack from './MitochondrialGeneCoverageTrack' import MitochondrialVariantsInGene from './MitochondrialVariantsInGene' import { getPreferredTranscript } from './preferredTranscript' import StructuralVariantsInGene from './StructuralVariantsInGene' -import TissueExpressionTrack from './TissueExpressionTrack' +import TissueExpressionTrack, { TranscriptWithTissueExpression } from './TissueExpressionTrack' import VariantsInGene from './VariantsInGene' import { GnomadConstraint } from '../ConstraintTable/GnomadConstraintTable' @@ -71,6 +72,8 @@ import { LegendSwatch, } from '../ChartStyles' import { logButtonClick } from '../analytics' +import { GtexTissueExpression } from './TranscriptsTissueExpression' +import { GTEX_TISSUES } from '../gtex' export type Strand = '+' | '-' @@ -88,6 +91,30 @@ export type GeneMetadata = { flags: string[] } +export type GeneTranscript = { + transcript_id: string + transcript_version: string + exons: { + feature_type: string + start: number + stop: number + }[] + gtex_tissue_expression: GtexTissueExpression | null +} + +export type Pext = { + regions: { + start: number + stop: number + mean: number + tissues: { + tissue: string + value: number + }[] + }[] + flags: string[] +} + export type Gene = GeneMetadata & { reference_genome: ReferenceGenome name?: string @@ -100,29 +127,11 @@ export type Gene = GeneMetadata & { start: number stop: number }[] - transcripts: { - transcript_id: string - transcript_version: string - exons: { - feature_type: string - start: number - stop: number - }[] - }[] + transcripts: GeneTranscript[] flags: string[] gnomad_constraint?: GnomadConstraint exac_constraint?: ExacConstraint - pext?: { - regions: { - start: number - stop: number - mean: number - tissues: { - [key: string]: number - } - }[] - flags: string[] - } + pext?: Pext short_tandem_repeats?: { id: string }[] @@ -517,6 +526,8 @@ const GenePage = ({ datasetId, gene, geneId }: Props) => { <TrackWrapper> <GeneTranscriptsTrack datasetId={datasetId} + isTissueExpressionAvailable={!!gene.pext} + gtexTissues={GTEX_TISSUES[getTopLevelDataset(datasetId)]} gene={gene} includeNonCodingTranscripts={includeNonCodingTranscripts} includeUTRs={includeUTRs} @@ -528,11 +539,12 @@ const GenePage = ({ datasetId, gene, geneId }: Props) => { {hasCodingExons && gene.chrom !== 'M' && gene.pext && ( <TissueExpressionTrack - datasetId={datasetId} + gtexTissues={GTEX_TISSUES[getTopLevelDataset(datasetId)]} exons={cdsCompositeExons} expressionRegions={gene.pext.regions} flags={gene.pext.flags} - transcripts={gene.transcripts} + // if a gene has pext, it also has GTEx + transcripts={gene.transcripts as TranscriptWithTissueExpression[]} // if a gene has pext, preferredTranscriptId={preferredTranscriptId} preferredTranscriptDescription={preferredTranscriptDescription} /> @@ -564,7 +576,6 @@ const GenePage = ({ datasetId, gene, geneId }: Props) => { <VariantsInGene datasetId={datasetId} gene={gene} - // @ts-expect-error TS(2322) FIXME: Type '{ datasetId: string; gene: { gene_id: string... Remove this comment to see the full error message includeNonCodingTranscripts={includeNonCodingTranscripts} includeUTRs={includeUTRs} zoomRegion={zoomRegion} diff --git a/browser/src/GenePage/GeneTranscriptsTrack.tsx b/browser/src/GenePage/GeneTranscriptsTrack.tsx index 3e3d197b1..0116273d2 100644 --- a/browser/src/GenePage/GeneTranscriptsTrack.tsx +++ b/browser/src/GenePage/GeneTranscriptsTrack.tsx @@ -8,18 +8,14 @@ import { Track } from '@gnomad/region-viewer' import TranscriptsTrack from '@gnomad/track-transcripts' import { Button, Modal, TooltipAnchor } from '@gnomad/ui' -import { GTEX_TISSUES } from '../gtex' +import { AllGtexTissues, TissueDetail } from '../gtex' import InfoButton from '../help/InfoButton' import Link from '../Link' import sortedTranscripts from './sortedTranscripts' import TranscriptsTissueExpression from './TranscriptsTissueExpression' import { Gene } from './GenePage' -import { - DatasetId, - getTopLevelDataset, - hasStructuralVariants, -} from '../../../dataset-metadata/metadata' -import { Transcript } from '../TranscriptPage/TranscriptPage' +import { DatasetId, hasStructuralVariants } from '../../../dataset-metadata/metadata' +import { TranscriptWithTissueExpression } from './TissueExpressionTrack' const TranscriptsInfoWrapper = styled.div` display: flex; @@ -41,6 +37,8 @@ const RightPanel = styled.div` type GeneTranscriptsTrack = { datasetId: DatasetId + isTissueExpressionAvailable: boolean + gtexTissues: Partial<AllGtexTissues> & { [key: string]: TissueDetail | undefined } gene: Gene includeNonCodingTranscripts: boolean includeUTRs: boolean @@ -50,6 +48,8 @@ type GeneTranscriptsTrack = { const GeneTranscriptsTrack = ({ datasetId, + isTissueExpressionAvailable, + gtexTissues, gene, includeNonCodingTranscripts, includeUTRs, @@ -57,17 +57,12 @@ const GeneTranscriptsTrack = ({ preferredTranscriptDescription, }: GeneTranscriptsTrack) => { const transcriptsTrack = useRef(null) - - const gtexTissues = GTEX_TISSUES[getTopLevelDataset(datasetId)] - - const isTissueExpressionAvailable = gene.reference_genome === 'GRCh37' const [showTissueExpressionModal, setShowTissueExpressionModal] = useState(false) const maxMeanExpression = isTissueExpressionAvailable ? max( - // @ts-ignore - gene.transcripts.map((transcript: Transcript) => - mean(transcript.gtex_tissue_expression!.map((tissue) => tissue.value)) + (gene.transcripts as TranscriptWithTissueExpression[]).map( + (transcript) => mean(transcript.gtex_tissue_expression.map((tissue) => tissue.value))! ) ) : undefined @@ -133,15 +128,36 @@ const GeneTranscriptsTrack = ({ renderTranscriptRightPanel={ !isTissueExpressionAvailable ? null - : ({ transcript, width }: any) => { + : ({ + transcript, + width, + }: { + transcript: TranscriptWithTissueExpression + width: number + }) => { if (width < 36) { return null } - const meanExpression = mean(Object.values(transcript.gtex_tissue_expression)) - const maxExpression = max(Object.values(transcript.gtex_tissue_expression)) - const tissueMostExpressedIn = Object.keys(transcript.gtex_tissue_expression).find( - (tissue: any) => transcript.gtex_tissue_expression[tissue] === maxExpression + const meanExpression = mean( + transcript.gtex_tissue_expression.map( + (tissueExpression) => tissueExpression.value + ) + )! + const maxExpression = max( + transcript.gtex_tissue_expression.map( + (tissueExpression) => tissueExpression.value + ) + )! + const tissueMostExpressedIn = transcript.gtex_tissue_expression.find( + (tissue) => tissue.value === maxExpression + )!.tissue + + const circleRadiusMeanContribution = meanExpression === 0 ? 0 : 0.25 + const circleRadiusMaxMeanContribution = + maxMeanExpression === 0 ? 0 : meanExpression / maxMeanExpression! // if the right panel render, maxMean is defined + const circleRadius = Math.sqrt( + circleRadiusMeanContribution + 23.75 * circleRadiusMaxMeanContribution ) return ( @@ -150,10 +166,9 @@ const GeneTranscriptsTrack = ({ // @ts-expect-error TS(2322) FIXME: Type '{ children: Element; tooltip: string; }' is ... Remove this comment to see the full error message tooltip={`Mean expression across all tissues = ${meanExpression.toFixed( 2 - )} TPM\nMost expressed in ${gtexTissues[tissueMostExpressedIn].fullName} (${ - // @ts-expect-error TS(2532) FIXME: Object is possibly 'undefined'. - maxExpression.toFixed(2) - } TPM)`} + )} TPM\nMost expressed in ${ + gtexTissues[tissueMostExpressedIn]!.fullName + } (${maxExpression.toFixed(2)} TPM)`} > <rect x={0} @@ -164,20 +179,7 @@ const GeneTranscriptsTrack = ({ pointerEvents="visible" /> </TooltipAnchor> - <circle - cx={15} - cy={5} - r={Math.sqrt( - meanExpression === 0 - ? 0 - : 0.25 + - 23.75 * - // @ts-expect-error TS(2367) FIXME: This condition will always return 'false' since th... Remove this comment to see the full error message - (maxMeanExpression === 0 ? 0 : meanExpression / maxMeanExpression) - )} - fill="#333" - pointerEvents="none" - /> + <circle cx={15} cy={5} r={circleRadius} fill="#333" pointerEvents="none" /> </svg> ) } @@ -196,7 +198,8 @@ const GeneTranscriptsTrack = ({ }} > <TranscriptsTissueExpression - transcripts={gene.transcripts} + gtexTissues={gtexTissues} + transcripts={gene.transcripts as TranscriptWithTissueExpression[]} includeNonCodingTranscripts={includeNonCodingTranscripts} preferredTranscriptId={preferredTranscriptId} preferredTranscriptDescription={preferredTranscriptDescription} diff --git a/browser/src/GenePage/TissueExpressionTrack.tsx b/browser/src/GenePage/TissueExpressionTrack.tsx index c44ec8b1e..09af63f25 100644 --- a/browser/src/GenePage/TissueExpressionTrack.tsx +++ b/browser/src/GenePage/TissueExpressionTrack.tsx @@ -9,15 +9,13 @@ import { Track } from '@gnomad/region-viewer' import { RegionsPlot } from '@gnomad/track-regions' import { Badge, Button, Modal, SearchInput, Select, TooltipAnchor } from '@gnomad/ui' -import { Transcript } from '../TranscriptPage/TranscriptPage' - -import { GTEX_TISSUES } from '../gtex' +import { AllGtexTissues } from '../gtex' import InfoButton from '../help/InfoButton' import { logButtonClick } from '../analytics' -import TranscriptsTissueExpression from './TranscriptsTissueExpression' -import { DatasetId, getTopLevelDataset } from '@gnomad/dataset-metadata/metadata' +import TranscriptsTissueExpression, { GtexTissueExpression } from './TranscriptsTissueExpression' +import { GeneTranscript } from './GenePage' const getPlotRegions = (expressionRegions: any, getValueForRegion: any) => { const roundedRegions = expressionRegions.map((region: any) => ({ @@ -70,7 +68,8 @@ const TRACK_HEIGHT = 20 const heightScale = scaleLinear().domain([0, 1]).range([0, TRACK_HEIGHT]).clamp(true) type PextRegionsPlotProps = { - datasetId: DatasetId + // datasetId: DatasetId + gtexTissues: Partial<AllGtexTissues> color: string regions: { start: number @@ -155,7 +154,8 @@ type ExpressedTissue = { } type IndividualTissueTrackProps = { - datasetId: DatasetId + // datasetId: DatasetId + gtexTissues: Partial<AllGtexTissues> exons: { start: number stop: number @@ -170,14 +170,14 @@ type IndividualTissueTrackProps = { maxMeanTranscriptExpressionInAnyTissue: number meanTranscriptExpressionInTissue: number tissue: string - transcriptWithMaxExpressionInTissue?: { + transcriptWithMaxExpressionInTissue: { transcript_id: string transcript_version: string - } + } | null } const IndividualTissueTrack = ({ - datasetId, + gtexTissues, exons, expressionRegions, maxTranscriptExpressionInTissue, @@ -186,7 +186,6 @@ const IndividualTissueTrack = ({ tissue, transcriptWithMaxExpressionInTissue, }: IndividualTissueTrackProps) => { - const gtexTissuesForDataset = GTEX_TISSUES[getTopLevelDataset(datasetId)] const isExpressed = expressionRegions.some( (region: any) => region.tissues.find((tissueObject: ExpressedTissue) => tissueObject.tissue === tissue) @@ -221,8 +220,8 @@ const IndividualTissueTrack = ({ 2 )} TPM\nMax transcript expression in this tissue = ${maxTranscriptExpressionInTissue.toFixed( 2 - )} (${transcriptWithMaxExpressionInTissue.transcript_id}.${ - transcriptWithMaxExpressionInTissue.transcript_version + )} (${transcriptWithMaxExpressionInTissue!.transcript_id}.${ + transcriptWithMaxExpressionInTissue!.transcript_version })` : // @ts-expect-error TS(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message `Gene is not expressed in ${gtexTissuesForDataset[tissue].fullName}` @@ -258,9 +257,9 @@ const IndividualTissueTrack = ({ return ( <PlotWrapper key={tissue}> <PextRegionsPlot - datasetId={datasetId} + gtexTissues={gtexTissues} // @ts-expect-error TS(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message - color={gtexTissuesForDataset[tissue].color} + color={gtexTissues[tissue].color} regions={getPlotRegions( expressionRegions, (r: any) => @@ -325,8 +324,13 @@ const RightPanel = styled.div` margin-top: 1.25em; ` +// Omit gtex then re-include to remove the possible null, as this component only renders if gtex and pext are non null +export type TranscriptWithTissueExpression = Omit<GeneTranscript, 'gtex_tissue_expression'> & { + gtex_tissue_expression: GtexTissueExpression +} + type TissueExpressionTrackProps = { - datasetId: DatasetId + gtexTissues: Partial<AllGtexTissues> exons: { start: number stop: number @@ -341,13 +345,13 @@ type TissueExpressionTrackProps = { }[] }[] flags: string[] - transcripts: Transcript[] + transcripts: TranscriptWithTissueExpression[] preferredTranscriptId?: string preferredTranscriptDescription?: string | React.ReactNode } const TissueExpressionTrack = ({ - datasetId, + gtexTissues, exons, expressionRegions, flags, @@ -360,38 +364,43 @@ const TissueExpressionTrack = ({ useState(false) const [tissueFilterText, setTissueFilterText] = useState('') const mainTrack = useRef() - - const gtexTissuesInDataset = GTEX_TISSUES[getTopLevelDataset(datasetId)] - - const [sortTissuesBy, setSortTissuesBy] = useState('alphabetical') + const [sortTissuesBy, setSortTissuesBy] = useState<'alphabetical' | 'mean-expression'>( + 'alphabetical' + ) type ExpressionByTissueDetails = { maxTranscriptExpressionInTissue: number meanTranscriptExpressionInTissue: number - transcriptWithMaxExpressionInTissue: string + transcriptWithMaxExpressionInTissue: { + transcript_id: string + transcript_version: string + } | null } type ExpressionByTissue = Record<string, ExpressionByTissueDetails> - const expressionByTissue: ExpressionByTissue = Object.keys(gtexTissuesInDataset).reduce( + const expressionByTissue: ExpressionByTissue = Object.keys(gtexTissues).reduce( (acc, tissueId) => { let maxTranscriptExpressionInTissue = 0 let transcriptWithMaxExpressionInTissue = null - transcripts.forEach((transcript: Transcript) => { - const expressionInTissue = transcript.gtex_tissue_expression!.find( + transcripts.forEach((transcript) => { + const expressionInTissue = transcript.gtex_tissue_expression.find( (tissue) => tissue.tissue === tissueId )!.value - if (expressionInTissue! > maxTranscriptExpressionInTissue) { + if (expressionInTissue > maxTranscriptExpressionInTissue) { maxTranscriptExpressionInTissue = expressionInTissue! - transcriptWithMaxExpressionInTissue = transcript + transcriptWithMaxExpressionInTissue = { + transcript_id: transcript.transcript_id, + transcript_version: transcript.transcript_version, + } } }) const meanTranscriptExpressionInTissue = mean( transcripts.map( - (transcript: Transcript) => - transcript.gtex_tissue_expression!.find((tissue) => tissue.tissue === tissueId)!.value + (transcript) => + transcript.gtex_tissue_expression.find((tissue) => tissue.tissue === tissueId)!.value ) ) @@ -409,11 +418,11 @@ const TissueExpressionTrack = ({ const maxMeanTranscriptExpressionInAnyTissue = max( Object.values(expressionByTissue).map((v) => v.meanTranscriptExpressionInTissue) - ) + )! let tissues if (sortTissuesBy === 'mean-expression') { - tissues = Object.entries(gtexTissuesInDataset) + tissues = Object.entries(gtexTissues) .sort((t1, t2) => { const t1Expression = expressionByTissue[t1[0]].meanTranscriptExpressionInTissue const t2Expression = expressionByTissue[t2[0]].meanTranscriptExpressionInTissue @@ -424,7 +433,7 @@ const TissueExpressionTrack = ({ }) .map((t: any) => t[0]) } else { - tissues = Object.entries(gtexTissuesInDataset) + tissues = Object.entries(gtexTissues) .sort((t1, t2) => t1[1].fullName.localeCompare(t2[1].fullName)) .map((t) => t[0]) } @@ -493,6 +502,7 @@ const TissueExpressionTrack = ({ return ( <PlotWrapper> <PextRegionsPlot + gtexTissues={gtexTissues} color="#428bca" regions={getPlotRegions(expressionRegions, (r: any) => r.mean)} scalePosition={scalePosition} @@ -571,21 +581,18 @@ const TissueExpressionTrack = ({ {(tissueFilterText ? tissues.filter(tissuePredicate(tissueFilterText)) : tissues).map( (tissue: any) => ( <IndividualTissueTrack - datasetId={datasetId} + gtexTissues={gtexTissues} key={tissue} exons={exons} expressionRegions={expressionRegions} maxTranscriptExpressionInTissue={ - // @ts-expect-error TS(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message expressionByTissue[tissue].maxTranscriptExpressionInTissue } maxMeanTranscriptExpressionInAnyTissue={maxMeanTranscriptExpressionInAnyTissue} meanTranscriptExpressionInTissue={ - // @ts-expect-error TS(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message expressionByTissue[tissue].meanTranscriptExpressionInTissue } transcriptWithMaxExpressionInTissue={ - // @ts-expect-error TS(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message expressionByTissue[tissue].transcriptWithMaxExpressionInTissue } tissue={tissue} @@ -618,7 +625,7 @@ const TissueExpressionTrack = ({ }} > <TranscriptsTissueExpression - datasetId={datasetId} + gtexTissues={gtexTissues} transcripts={transcripts} includeNonCodingTranscripts preferredTranscriptId={preferredTranscriptId} diff --git a/browser/src/GenePage/TranscriptsTissueExpression.tsx b/browser/src/GenePage/TranscriptsTissueExpression.tsx index eda7c6759..730944fb1 100644 --- a/browser/src/GenePage/TranscriptsTissueExpression.tsx +++ b/browser/src/GenePage/TranscriptsTissueExpression.tsx @@ -4,11 +4,11 @@ import styled from 'styled-components' import { Select } from '@gnomad/ui' -import { GTEX_TISSUES } from '../gtex' +import { AllGtexTissues } from '../gtex' import sortedTranscripts from './sortedTranscripts' import TranscriptsTissueExpressionPlot from './TranscriptsTissueExpressionPlot' -import { DatasetId, getTopLevelDataset } from '@gnomad/dataset-metadata/metadata' +import { TranscriptWithTissueExpression } from './TissueExpressionTrack' const ScrollWrapper = styled.div` display: inline-block; @@ -21,17 +21,8 @@ const isTranscriptCoding = (transcript: any) => transcript.exons.some((exon: any) => exon.feature_type === 'CDS') type TranscriptsTissueExpressionProps = { - datasetId: DatasetId - transcripts: Transcript[] - // transcripts: { - // transcript_id: string - // transcript_version: string - // exons: { - // feature_type: string - // start: number - // stop: number - // }[] - // }[] + gtexTissues: Partial<AllGtexTissues> + transcripts: TranscriptWithTissueExpression[] includeNonCodingTranscripts: boolean preferredTranscriptId?: string preferredTranscriptDescription?: string | React.ReactNode @@ -46,7 +37,7 @@ export type TissueExpression = { export type GtexTissueExpression = TissueExpression[] const TranscriptsTissueExpression = ({ - datasetId, + gtexTissues, transcripts, includeNonCodingTranscripts, preferredTranscriptId, @@ -54,7 +45,6 @@ const TranscriptsTissueExpression = ({ defaultSortTissuesBy, }: TranscriptsTissueExpressionProps) => { const [sortTranscriptsBy, setSortTranscriptsBy] = useState('default') - const gtexTissuesForDataset = GTEX_TISSUES[getTopLevelDataset(datasetId)] let renderedTranscripts = sortTranscriptsBy === 'default' @@ -63,11 +53,11 @@ const TranscriptsTissueExpression = ({ const t1Expression = t1.gtex_tissue_expression.find( (tissue: TissueExpression) => tissue.tissue === sortTranscriptsBy - ).value || 0 + )!.value || 0 const t2Expression = t2.gtex_tissue_expression.find( (tissue: TissueExpression) => tissue.tissue === sortTranscriptsBy - ).value || 0 + )!.value || 0 if (t1Expression === t2Expression) { return t1.transcript_id.localeCompare(t2.transcript_id) @@ -79,9 +69,7 @@ const TranscriptsTissueExpression = ({ const [sortTissuesBy, setSortTissuesBy] = useState(defaultSortTissuesBy) let tissues if (sortTissuesBy === 'mean-expression') { - const meanExpressionByTissue: Record<string, number> = Object.keys( - gtexTissuesForDataset - ).reduce( + const meanExpressionByTissue: Record<string, number> = Object.keys(gtexTissues).reduce( (acc, tissueId) => ({ ...acc, [tissueId]: mean( @@ -95,7 +83,7 @@ const TranscriptsTissueExpression = ({ }), {} ) - tissues = Object.entries(gtexTissuesForDataset) + tissues = Object.entries(gtexTissues) .sort((t1, t2) => { const t1Expression = meanExpressionByTissue[t1[0]] const t2Expression = meanExpressionByTissue[t2[0]] @@ -106,7 +94,7 @@ const TranscriptsTissueExpression = ({ }) .map((t: any) => t[0]) } else { - tissues = Object.entries(gtexTissuesForDataset) + tissues = Object.entries(gtexTissues) .sort((t1, t2) => t1[1].fullName.localeCompare(t2[1].fullName)) .map((t) => t[0]) } @@ -128,7 +116,7 @@ const TranscriptsTissueExpression = ({ > <option value="default">Default</option> <optgroup label="Expression in tissue"> - {Object.entries(gtexTissuesForDataset).map(([tissueId, tissueDetails]) => { + {Object.entries(gtexTissues).map(([tissueId, tissueDetails]) => { return ( <option key={tissueId} value={tissueId}> {tissueDetails.fullName} @@ -155,9 +143,9 @@ const TranscriptsTissueExpression = ({ {preferredTranscriptDescription && <p>* {preferredTranscriptDescription}</p>} <ScrollWrapper> <TranscriptsTissueExpressionPlot - datasetId={datasetId} + gtexTissues={gtexTissues} tissues={tissues} - transcripts={renderedTranscripts} + transcripts={renderedTranscripts as TranscriptWithTissueExpression[]} starredTranscriptId={preferredTranscriptId} /> </ScrollWrapper> diff --git a/browser/src/GenePage/TranscriptsTissueExpressionPlot.tsx b/browser/src/GenePage/TranscriptsTissueExpressionPlot.tsx index 90f15a9c6..12c03c33b 100644 --- a/browser/src/GenePage/TranscriptsTissueExpressionPlot.tsx +++ b/browser/src/GenePage/TranscriptsTissueExpressionPlot.tsx @@ -3,12 +3,9 @@ import { scaleBand, scaleLinear, scaleOrdinal } from 'd3-scale' import React from 'react' import { AxisBottom, AxisLeft } from '@visx/axis' -import { Transcript } from '../TranscriptPage/TranscriptPage' - import { TooltipAnchor } from '@gnomad/ui' - -import { AllGtexTissues, GTEX_TISSUES } from '../gtex' -import { DatasetId, getTopLevelDataset } from '@gnomad/dataset-metadata/metadata' +import { AllGtexTissueNames, AllGtexTissues } from '../gtex' +import { TranscriptWithTissueExpression } from './TissueExpressionTrack' const mergeOverlappingRegions = (regions: any) => { if (regions.length === 0) { @@ -124,33 +121,34 @@ const margin = { } type TranscriptsTissueExpressionPlotProps = { - datasetId: DatasetId - tissues?: AllGtexTissues[] - transcripts: Transcript[] + gtexTissues: Partial<AllGtexTissues> + tissues?: AllGtexTissueNames[] + transcripts: TranscriptWithTissueExpression[] starredTranscriptId?: string } const TranscriptsTissueExpressionPlot = ({ - datasetId, + gtexTissues, tissues, transcripts, starredTranscriptId, }: TranscriptsTissueExpressionPlotProps) => { - const renderedTissues: (AllGtexTissues | 'Mean' | 'Median')[] = ['Mean', 'Median', ...tissues!] - const gtexTissuesForDataset = GTEX_TISSUES[getTopLevelDataset(datasetId)] + type RenderedTissue = AllGtexTissueNames | 'Mean' | 'Median' + const renderedTissues: RenderedTissue[] = ['Mean', 'Median', ...tissues!] const transcriptsWithMeanAndMedianExpression = transcripts.map((transcript) => { - const gtexTissueExpressionObject = transcript.gtex_tissue_expression!.reduce((acc, tissue) => { - acc[tissue.tissue] = tissue.value - return acc - }, {} as Record<string, number>) + const gtexTissueExpressionObject: { [key: string]: number } = + transcript.gtex_tissue_expression.reduce((acc, tissue) => { + acc[tissue.tissue] = tissue.value + return acc + }, {} as Record<string, number>) - const expressionValues = transcript.gtex_tissue_expression!.map((tissue) => tissue.value) + const expressionValues = transcript.gtex_tissue_expression.map((tissue) => tissue.value) return { ...transcript, gtex_tissue_expression: { - ...gtexTissueExpressionObject, + ...(gtexTissueExpressionObject as Partial<Record<AllGtexTissueNames, number>>), Mean: mean(expressionValues), Median: median(expressionValues), }, @@ -158,12 +156,11 @@ const TranscriptsTissueExpressionPlot = ({ }) const maxTissueExpression = max( - transcripts.flatMap((transcript: Transcript) => - transcript.gtex_tissue_expression!.map((tissue) => tissue.value) + transcripts.flatMap((transcript) => + transcript.gtex_tissue_expression.map((tissue) => tissue.value) ) - ) + )! - // @ts-expect-error TS(2345) FIXME: Argument of type '(string | number | undefined)[]'... Remove this comment to see the full error message const opacityScale = scaleLinear().domain([0, maxTissueExpression]).range([0, 1]) const transcriptsWidth = 150 @@ -186,8 +183,7 @@ const TranscriptsTissueExpressionPlot = ({ .domain(renderedTissues) .range([ ...renderedTissues.slice(0, 2).map(baseXScale), - // @ts-expect-error TS(2532) FIXME: Object is possibly 'undefined'. - ...renderedTissues.slice(2).map((tissueId) => baseXScale(tissueId) + gutterWidth), + ...renderedTissues.slice(2).map((tissueId) => baseXScale(tissueId)! + gutterWidth), ]) const xAxisScale = scaleOrdinal() @@ -195,14 +191,12 @@ const TranscriptsTissueExpressionPlot = ({ .range( [ ...renderedTissues.slice(0, 2).map(baseXScale), - // @ts-expect-error TS(2532) FIXME: Object is possibly 'undefined'. - ...renderedTissues.slice(2).map((tissueId) => baseXScale(tissueId) + gutterWidth), - // @ts-expect-error TS(2532) FIXME: Object is possibly 'undefined'. - ].map((x) => x + xBandWidth / 2) + ...renderedTissues.slice(2).map((tissueId) => baseXScale(tissueId)! + gutterWidth), + ].map((x) => x! + xBandWidth / 2) ) const yScale = scaleBand() - .domain(transcriptsWithMeanAndMedianExpression.map((t: any) => t.transcript_id)) + .domain(transcriptsWithMeanAndMedianExpression.map((transcript) => transcript.transcript_id)) .range([0, plotHeight]) .padding(padding) @@ -210,7 +204,7 @@ const TranscriptsTissueExpressionPlot = ({ const halfYPadding = (yScale.step() * yScale.paddingInner()) / 2 - const transcriptLabels = transcripts.reduce( + const transcriptLabels: { [key: string]: string } = transcripts.reduce( (acc, transcript) => ({ ...acc, [transcript.transcript_id]: `${transcript.transcript_id}.${transcript.transcript_version}`, @@ -236,14 +230,12 @@ const TranscriptsTissueExpressionPlot = ({ </text> <rect x={10} y={0} height={14} width={60} fill="url(#expression-gradient)" /> <text x={70} y={7} dx="0.25em" dy="0.25em" fontSize={10} textAnchor="start"> - {/* @ts-expect-error TS(2532) FIXME: Object is possibly 'undefined'. */} {maxTissueExpression.toPrecision(4)} </text> </g> - {transcripts.slice(1).map((transcript: any) => { - // @ts-expect-error TS(2532) FIXME: Object is possibly 'undefined'. - const y = margin.top + yScale(transcript.transcript_id) - halfYPadding + {transcripts.slice(1).map((transcript) => { + const y = margin.top + yScale(transcript.transcript_id)! - halfYPadding return ( <line @@ -287,9 +279,8 @@ const TranscriptsTissueExpressionPlot = ({ // @ts-expect-error TS(2322) FIXME: Type 'ScaleOrdinal<string, unknown, never>' is not... Remove this comment to see the full error message scale={xAxisScale} stroke="#333" - // @ts-expect-error TS(7053) FIXME: Element implicitly has an 'any' type because expre... Remove this comment to see the full error message - tickFormat={(t) => { - return gtexTissuesForDataset[t] ? gtexTissuesForDataset[t].fullName : t + tickFormat={(tissue: AllGtexTissueNames) => { + return gtexTissues[tissue] ? gtexTissues[tissue]!.fullName : tissue }} tickLabelProps={(value) => ({ dx: '-0.25em', @@ -297,8 +288,9 @@ const TranscriptsTissueExpressionPlot = ({ fill: '#000', fontSize: 10, textAnchor: 'end', - // @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'. - transform: `translate(0, 0), rotate(-40 ${xScale(value) + xBandWidth / 2}, 0)`, + transform: `translate(0, 0), rotate(-40 ${ + (xScale(value) as number) + xBandWidth / 2 + }, 0)`, })} tickLength={3} /> @@ -318,8 +310,8 @@ const TranscriptsTissueExpressionPlot = ({ )} TPM` } else { tooltipText = `${transcript.transcript_id} expression in ${ - gtexTissuesForDataset[tissueId].fullName - } tissues: ${transcript.gtex_tissue_expression[tissueId].toFixed(2)} TPM` + gtexTissues[tissueId]!.fullName + } tissues: ${transcript.gtex_tissue_expression[tissueId]!.toFixed(2)} TPM` } return ( @@ -332,7 +324,7 @@ const TranscriptsTissueExpressionPlot = ({ height={yBandWidth - 2} rx={3} fill="#3f007d" - opacity={opacityScale(transcript.gtex_tissue_expression[tissueId])} + opacity={opacityScale(transcript.gtex_tissue_expression[tissueId]!)} /> {/* @ts-expect-error TS(2322) FIXME: Type '{ children: Element; key: any; tooltip: stri... Remove this comment to see the full error message */} <TooltipAnchor key={tissueId} tooltip={tooltipText}> diff --git a/browser/src/GenePage/VariantsInGene.tsx b/browser/src/GenePage/VariantsInGene.tsx index 7a727aad4..da367cf91 100644 --- a/browser/src/GenePage/VariantsInGene.tsx +++ b/browser/src/GenePage/VariantsInGene.tsx @@ -11,6 +11,7 @@ import filterVariantsInZoomRegion from '../RegionViewer/filterVariantsInZoomRegi import { TrackPageSection } from '../TrackPage' import annotateVariantsWithClinvar from '../VariantList/annotateVariantsWithClinvar' import Variants from '../VariantList/Variants' +import { Pext } from './GenePage' type TranscriptsModalProps = { gene: { @@ -326,18 +327,9 @@ type ConnectedVariantsInGeneProps = { datasetId: DatasetId gene: { gene_id: string - pext?: { - regions: { - start: number - stop: number - mean: number - tissues: { - [key: string]: number - } - }[] - } + pext?: Pext } -} +} & VariantsInGeneProps const ConnectedVariantsInGene = ({ datasetId, diff --git a/browser/src/GenePage/sortedTranscripts.ts b/browser/src/GenePage/sortedTranscripts.ts index 812fbb4c9..59ff69f7f 100644 --- a/browser/src/GenePage/sortedTranscripts.ts +++ b/browser/src/GenePage/sortedTranscripts.ts @@ -1,7 +1,19 @@ import { mean } from 'd3-array' -import { Transcript } from '../TranscriptPage/TranscriptPage' -const sortedTranscripts = (transcripts: Transcript[], firstTranscriptId: string | undefined) => { +interface GenericTranscript { + transcript_id: string + gtex_tissue_expression: + | { + tissue: string + value: number + }[] + | null +} + +const sortedTranscripts = ( + transcripts: GenericTranscript[], + firstTranscriptId: string | undefined +) => { return [...transcripts].sort((t1, t2) => { // Sort specified transcript first // Then sort transcripts by mean expression and transcript ID diff --git a/browser/src/gtex.ts b/browser/src/gtex.ts index 3cb4c0722..e93a4faa6 100644 --- a/browser/src/gtex.ts +++ b/browser/src/gtex.ts @@ -209,9 +209,9 @@ const SHARED_GTEX_TISSUES = { }, } -type SharedTissues = keyof typeof SHARED_GTEX_TISSUES +type SharedGtexTissueNames = keyof typeof SHARED_GTEX_TISSUES -type TissueDetail = { +export type TissueDetail = { fullName: string color: string } @@ -223,9 +223,9 @@ const V2_SPECIFIC_GTEX_TISSUES = { }, } -type V2GtexTissues = SharedTissues | keyof typeof V2_SPECIFIC_GTEX_TISSUES +type V2GtexTissueNames = SharedGtexTissueNames | keyof typeof V2_SPECIFIC_GTEX_TISSUES -const V2_GTEX_TISSUES: Record<V2GtexTissues, TissueDetail> = { +const V2_GTEX_TISSUES: Record<V2GtexTissueNames, TissueDetail> = { ...SHARED_GTEX_TISSUES, ...V2_SPECIFIC_GTEX_TISSUES, } @@ -297,24 +297,26 @@ const V4_SPECIFIC_GTEX_TISSUES = { }, } -type V4GtexTissues = SharedTissues | keyof typeof V4_SPECIFIC_GTEX_TISSUES +type V4GtexTissueNames = SharedGtexTissueNames | keyof typeof V4_SPECIFIC_GTEX_TISSUES -export type AllGtexTissues = - | SharedTissues +export type AllGtexTissueNames = + | SharedGtexTissueNames | keyof typeof V2_SPECIFIC_GTEX_TISSUES | keyof typeof V4_SPECIFIC_GTEX_TISSUES -const V4_GTEX_TISSUES: Record<V4GtexTissues, TissueDetail> = { +export type AllGtexTissues = Record<AllGtexTissueNames, TissueDetail> + +const V4_GTEX_TISSUES: Record<V4GtexTissueNames, TissueDetail> = { ...SHARED_GTEX_TISSUES, ...V4_SPECIFIC_GTEX_TISSUES, } export const GTEX_TISSUES: { - v2: Record<V2GtexTissues, TissueDetail> - v4: Record<V4GtexTissues, TissueDetail> - default: Record<SharedTissues, TissueDetail> - v3: Record<SharedTissues, TissueDetail> - ExAC: Record<SharedTissues, TissueDetail> + v2: Record<V2GtexTissueNames, TissueDetail> + v4: Record<V4GtexTissueNames, TissueDetail> + default: Record<SharedGtexTissueNames, TissueDetail> + v3: Record<SharedGtexTissueNames, TissueDetail> + ExAC: Record<SharedGtexTissueNames, TissueDetail> } = { v2: V2_GTEX_TISSUES, v4: V4_GTEX_TISSUES,