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 5002b270c..463f51cc9 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 @@ -55,7 +82,7 @@ type Props = { constrainedRegions: RegionWithUnclamped[] infobuttonTopic: string legend: ReactNode - tooltipComponent: any // TK any + tooltipComponent: React.ElementType colorFn: (region: R) => string valueFn: (region: R) => string } @@ -120,9 +147,12 @@ const ConstraintTrack = ({ > {({ scalePosition, width }: TrackProps) => ( <> - {legend} + + {legend} + + {!allRegions && } {constrainedRegions.map((region: RegionWithUnclamped) => { const startX = scalePosition(region.start) const stopX = scalePosition(region.stop) @@ -139,7 +169,7 @@ const ConstraintTrack = ({ ({ ) })} - - {allRegions && - allRegions.map((region: R, index: number) => { + {allRegions && ( + + {allRegions.map((region: R, index: number) => { const startX = scalePosition(region.start) const stopX = scalePosition(region.stop) const regionWidth = stopX - startX @@ -198,7 +228,8 @@ const ConstraintTrack = ({ ) })} - + + )} 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..98d89c4a8 --- /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 a0c064a3c..6a74e9b53 100644 --- a/browser/src/RegionalMissenseConstraintTrack.tsx +++ b/browser/src/RegionalMissenseConstraintTrack.tsx @@ -11,6 +11,7 @@ import ConstraintTrack, { SidePanel, PlotWrapper, regionsInExons, + RegionAttributeList, RegionWithUnclamped, } from './ConstraintTrack' @@ -28,24 +29,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', @@ -77,25 +60,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 @@ -116,12 +90,12 @@ const Legend = () => { - - + + Not significant (p > 1e-3) - + ) } @@ -279,7 +253,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} />