From f0c671c4a10a0f6ec1994cabbbe4fe56b549b243 Mon Sep 17 00:00:00 2001 From: Phil Darnowsky Date: Mon, 18 Mar 2024 10:53:24 -0400 Subject: [PATCH] Add mitochondrial regional constraint visualizations --- browser/package.json | 1 + browser/src/ConstraintTrack.tsx | 134 +++++++++++------- browser/src/GenePage/GenePage.tsx | 46 ++++++ browser/src/GenePage/GenePageContainer.tsx | 35 +++++ ...itochondrialRegionConstraintTrack.spec.tsx | 46 ++++++ .../MitochondrialRegionConstraintTrack.tsx | 74 ++++++++++ ...ondrialRegionConstraintTrack.spec.tsx.snap | 110 ++++++++++++++ .../src/RegionalMissenseConstraintTrack.tsx | 54 +++---- browser/src/__factories__/Gene.ts | 4 + ...ionalMissenseConstraintTrack.spec.tsx.snap | 20 +-- pnpm-lock.yaml | 3 + 11 files changed, 431 insertions(+), 96 deletions(-) create mode 100644 browser/src/GenePage/MitochondrialRegionConstraintTrack.spec.tsx create mode 100644 browser/src/GenePage/MitochondrialRegionConstraintTrack.tsx create mode 100644 browser/src/GenePage/__snapshots__/MitochondrialRegionConstraintTrack.spec.tsx.snap diff --git a/browser/package.json b/browser/package.json index 77803e4e3..6fa0a5d3b 100644 --- a/browser/package.json +++ b/browser/package.json @@ -23,6 +23,7 @@ "@gnomad/ui": "2.0.0", "@hot-loader/react-dom": "^17.0.0", "@visx/axis": "^3.0.0", + "@visx/group": "^3.0.0", "core-js": "3.5.0", "css-loader": "^6.7.3", "d3-array": "^1.2.4", diff --git a/browser/src/ConstraintTrack.tsx b/browser/src/ConstraintTrack.tsx index b1d3da9e2..dd70e3313 100644 --- a/browser/src/ConstraintTrack.tsx +++ b/browser/src/ConstraintTrack.tsx @@ -37,6 +37,33 @@ const TopPanel = styled.div` margin-bottom: 5px; ` +const LegendWrapper = styled.div` + display: flex; + + @media (max-width: 600px) { + flex-direction: column; + align-items: center; + } +` + +export const RegionAttributeList = styled.dl` + margin: 0; + + div { + margin-bottom: 0.25em; + } + + dt { + display: inline; + font-weight: bold; + } + + dd { + display: inline; + margin-left: 0.5em; + } +` + export interface GenericRegion { start: number stop: number @@ -44,11 +71,11 @@ export interface GenericRegion { type Props = { trackTitle: string - allRegions: R[] + allRegions: R[] | null constrainedRegions: R[] infobuttonTopic: string legend: ReactNode - tooltipComponent: any // TK any + tooltipComponent: React.ElementType colorFn: (region: R) => string valueFn: (region: R) => string } @@ -104,9 +131,12 @@ const ConstraintTrack = ({ > {({ scalePosition, width }: TrackProps) => ( <> - {legend} + + {legend} + + {!allRegions && } {constrainedRegions.map((region: R) => { const startX = scalePosition(region.start) const stopX = scalePosition(region.stop) @@ -117,13 +147,13 @@ const ConstraintTrack = ({ key={`${region.start}-${region.stop}`} // @ts-expect-error need to redefine TooltipAnchor to allow arbitrary props for the children type-safely region={region} - isTranscript={allRegions.length === 1} + isTranscript={allRegions && allRegions.length === 1} tooltipComponent={tooltipComponent} > ({ ) })} - - {allRegions.map((region: R, index: number) => { - const startX = scalePosition(region.start) - const stopX = scalePosition(region.stop) - const regionWidth = stopX - startX - const midX = (startX + stopX) / 2 - const offset = index * 0 - - return ( - - - - - {regionWidth > 40 && ( - <> - - - {valueFn(region)} - - - )} - - ) - })} - + {allRegions && ( + + {allRegions.map((region: R, index: number) => { + const startX = scalePosition(region.start) + const stopX = scalePosition(region.stop) + const regionWidth = stopX - startX + const midX = (startX + stopX) / 2 + const offset = index * 0 + + return ( + + + + + {regionWidth > 40 && ( + <> + + + {valueFn(region)} + + + )} + + ) + })} + + )} diff --git a/browser/src/GenePage/GenePage.tsx b/browser/src/GenePage/GenePage.tsx index a75190b10..c1c1c976c 100644 --- a/browser/src/GenePage/GenePage.tsx +++ b/browser/src/GenePage/GenePage.tsx @@ -10,6 +10,9 @@ import { Track } from '@gnomad/region-viewer' // @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module '@gno... Remove this comment to see the full error message import { TranscriptPlot } from '@gnomad/track-transcripts' import { Badge, Button } from '@gnomad/ui' +import MitochondrialRegionConstraintTrack, { + MitochondrialConstraintRegion, +} from './MitochondrialRegionConstraintTrack' import { DatasetId, @@ -72,6 +75,40 @@ import { } from '../ChartStyles' import { logButtonClick } from '../analytics' +type ProteinMitochondrialGeneConstraint = { + exp_lof: number + exp_mis: number + exp_syn: number + + obs_lof: number + obs_mis: number + obs_syn: number + + oe_lof: number + oe_lof_lower: number + oe_lof_upper: number + + oe_mis: number + oe_mis_lower: number + oe_mis_upper: number + + oe_syn: number + oe_syn_lower: number + oe_syn_upper: number +} + +type RNAMitochondrialGeneConstraint = { + observed: number + expected: number + oe: number + oe_upper: number + oe_lower: number +} + +type MitochondrialGeneConstraint = + | ProteinMitochondrialGeneConstraint + | RNAMitochondrialGeneConstraint + export type Strand = '+' | '-' export type GeneMetadata = { @@ -136,6 +173,8 @@ export type Gene = GeneMetadata & { clinvar_variants: ClinvarVariant[] homozygous_variant_cooccurrence_counts: HomozygousVariantCooccurrenceCountsPerSeverityAndAf heterozygous_variant_cooccurrence_counts: HeterozygousVariantCooccurrenceCountsPerSeverityAndAf + mitochondrial_constraint: MitochondrialGeneConstraint | null + mitochondrial_missense_constraint_regions: MitochondrialConstraintRegion[] | null } const GeneName = styled.span` @@ -528,6 +567,13 @@ const GenePage = ({ datasetId, gene, geneId }: Props) => { )} + {gene.chrom.startsWith('M') && ( + + )} + {hasCodingExons && gene.chrom !== 'M' && gene.pext && ( i, +} + +const exons: Exon[] = [ + { feature_type: 'CDS', start: 123, stop: 234 }, + { feature_type: 'UTR', start: 235, stop: 999 }, + { feature_type: 'CDS', start: 1000, stop: 1999 }, +] + +test('track has no unexpected changes when gene has constraint', () => { + const constraintRegions: MitochondrialConstraintRegion[] = [ + { start: 555, stop: 666, oe: 0.45, oe_lower: 0.37, oe_upper: 0.47 }, + { start: 777, stop: 888, oe: 0.56, oe_lower: 0.52, oe_upper: 0.59 }, + ] + const tree = renderer.create( + + + + ) + expect(tree).toMatchSnapshot() +}) + +test('track has no unexpected changes when no constraint for gene', () => { + const tree = renderer.create( + + + + ) + expect(tree).toMatchSnapshot() +}) diff --git a/browser/src/GenePage/MitochondrialRegionConstraintTrack.tsx b/browser/src/GenePage/MitochondrialRegionConstraintTrack.tsx new file mode 100644 index 000000000..756ea2c4f --- /dev/null +++ b/browser/src/GenePage/MitochondrialRegionConstraintTrack.tsx @@ -0,0 +1,74 @@ +import React from 'react' +import { Exon } from '../TranscriptPage/TranscriptPage' +import ConstraintTrack, { regionsInExons, RegionAttributeList } from '../ConstraintTrack' + +export type MitochondrialConstraintRegion = { + start: number + stop: number + oe: number + oe_upper: number + oe_lower: number +} + +type Props = { + constraintRegions: MitochondrialConstraintRegion[] | null + exons: Exon[] +} + +const constraintColor = '#fd8d3c' + +const Legend = () => ( + <> + Constrained region + + + + +) + +type TooltipProps = { + region: MitochondrialConstraintRegion +} + +const Tooltip = ({ region }: TooltipProps) => { + return ( + +
+
Coordinates:
+
{`m:${region.start}-${region.stop}`}
+
+
+
Missense observed/expected:
+
+ {region.oe.toFixed(3)} ({region.oe_lower.toFixed(3)}-{region.oe_upper.toFixed(3)}) +
+
+
+ ) +} + +const formattedOE = (region: MitochondrialConstraintRegion) => region.oe.toFixed(3) + +const MitochondrialConstraintRegionTrack = ({ constraintRegions, exons }: Props) => { + if (constraintRegions === null) { + return null + } + + return ( + } + valueFn={formattedOE} + colorFn={() => constraintColor} + tooltipComponent={Tooltip} + allRegions={null} + constrainedRegions={regionsInExons( + constraintRegions, + exons.filter((exon) => exon.feature_type === 'CDS') + )} + /> + ) +} + +export default MitochondrialConstraintRegionTrack diff --git a/browser/src/GenePage/__snapshots__/MitochondrialRegionConstraintTrack.spec.tsx.snap b/browser/src/GenePage/__snapshots__/MitochondrialRegionConstraintTrack.spec.tsx.snap new file mode 100644 index 000000000..0bc77cc47 --- /dev/null +++ b/browser/src/GenePage/__snapshots__/MitochondrialRegionConstraintTrack.spec.tsx.snap @@ -0,0 +1,110 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`track has no unexpected changes when gene has constraint 1`] = ` +
+
+
+
+
+ + Regional constraint + + +
+
+
+
+
+ + Constrained region + + + + +
+
+
+ + + +
+
+
+
+
+`; + +exports[`track has no unexpected changes when no constraint for gene 1`] = `null`; diff --git a/browser/src/RegionalMissenseConstraintTrack.tsx b/browser/src/RegionalMissenseConstraintTrack.tsx index a5a21809c..ef3d1a4e3 100644 --- a/browser/src/RegionalMissenseConstraintTrack.tsx +++ b/browser/src/RegionalMissenseConstraintTrack.tsx @@ -7,7 +7,12 @@ import Link from './Link' import InfoButton from './help/InfoButton' import { Gene } from './GenePage/GenePage' -import ConstraintTrack, { SidePanel, PlotWrapper, regionsInExons } from './ConstraintTrack' +import ConstraintTrack, { + SidePanel, + PlotWrapper, + regionsInExons, + RegionAttributeList, +} from './ConstraintTrack' export type RegionalMissenseConstraintRegion = { chrom: string @@ -23,24 +28,6 @@ export type RegionalMissenseConstraintRegion = { z_score: number | undefined } -const RegionAttributeList = styled.dl` - margin: 0; - - div { - margin-bottom: 0.25em; - } - - dt { - display: inline; - font-weight: bold; - } - - dd { - display: inline; - margin-left: 0.5em; - } -` - // https://colorbrewer2.org/#type=sequential&scheme=YlOrRd&n=5 const colorScale = { not_significant: '#e2e2e2', @@ -72,25 +59,16 @@ function regionColor(region: RegionalMissenseConstraintRegion) { return region.p_value > 0.001 ? colorScale.not_significant : color } -const LegendWrapper = styled.div` - display: flex; - - @media (max-width: 600px) { - flex-direction: column; - align-items: center; - } -` - const Legend = () => { return ( - + <> Missense observed/expected - - - - - + + + + + 0.0 @@ -111,12 +89,12 @@ const Legend = () => { - - + + Not significant (p > 1e-3) - + ) } @@ -274,7 +252,7 @@ const RegionalMissenseConstraintTrack = ({ regionalMissenseConstraint, gene }: P legend={} tooltipComponent={RegionTooltip} valueFn={formattedOE} - >
+ /> ) } diff --git a/browser/src/__factories__/Gene.ts b/browser/src/__factories__/Gene.ts index ea16913e2..ac3439647 100644 --- a/browser/src/__factories__/Gene.ts +++ b/browser/src/__factories__/Gene.ts @@ -39,6 +39,8 @@ const geneFactory = Factory.define(({ params, associations }) => { short_tandem_repeats = null, exac_regional_missense_constraint_regions = null, gnomad_v2_regional_missense_constraint = null, + mitochondrial_constraint = null, + mitochondrial_missense_constraint_regions = null, } = associations const heterozygous_variant_cooccurrence_counts = @@ -101,6 +103,8 @@ const geneFactory = Factory.define(({ params, associations }) => { short_tandem_repeats, exac_regional_missense_constraint_regions, gnomad_v2_regional_missense_constraint, + mitochondrial_constraint, + mitochondrial_missense_constraint_regions, } }) diff --git a/browser/src/__snapshots__/RegionalMissenseConstraintTrack.spec.tsx.snap b/browser/src/__snapshots__/RegionalMissenseConstraintTrack.spec.tsx.snap index 0403bfac4..321aabf30 100644 --- a/browser/src/__snapshots__/RegionalMissenseConstraintTrack.spec.tsx.snap +++ b/browser/src/__snapshots__/RegionalMissenseConstraintTrack.spec.tsx.snap @@ -66,7 +66,7 @@ exports[`RegionalMissenseConstraint has no unexpected changes when the RMC has e className="ConstraintTrack__TopPanel-sc-113yvfg-3 imJcbb" >
Missense observed/expected @@ -81,7 +81,7 @@ exports[`RegionalMissenseConstraint has no unexpected changes when the RMC has e stroke="#000" width={30} x={10} - y={0} + y={1} /> Not significant (p > 1e-3) @@ -211,7 +211,7 @@ exports[`RegionalMissenseConstraint has no unexpected changes when the RMC has e stroke="black" width={56} x={124} - y={0} + y={1} />