Skip to content

Commit 9a07f81

Browse files
Add mitochondrial regional constraint visualizations
1 parent 5d880cc commit 9a07f81

11 files changed

+378
-54
lines changed

browser/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"@gnomad/ui": "2.0.0",
2424
"@hot-loader/react-dom": "^17.0.0",
2525
"@visx/axis": "^3.0.0",
26+
"@visx/group": "^3.0.0",
2627
"core-js": "3.5.0",
2728
"css-loader": "^6.7.3",
2829
"d3-array": "^1.2.4",

browser/src/ConstraintTrack.tsx

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,33 @@ const TopPanel = styled.div`
3737
margin-bottom: 5px;
3838
`
3939

40+
const LegendWrapper = styled.div`
41+
display: flex;
42+
43+
@media (max-width: 600px) {
44+
flex-direction: column;
45+
align-items: center;
46+
}
47+
`
48+
49+
export const RegionAttributeList = styled.dl`
50+
margin: 0;
51+
52+
div {
53+
margin-bottom: 0.25em;
54+
}
55+
56+
dt {
57+
display: inline;
58+
font-weight: bold;
59+
}
60+
61+
dd {
62+
display: inline;
63+
margin-left: 0.5em;
64+
}
65+
`
66+
4067
export interface GenericRegion {
4168
start: number
4269
stop: number
@@ -55,7 +82,7 @@ type Props<R extends GenericRegion> = {
5582
constrainedRegions: RegionWithUnclamped<R>[]
5683
infobuttonTopic: string
5784
legend: ReactNode
58-
tooltipComponent: any // TK any
85+
tooltipComponent: React.ElementType
5986
colorFn: (region: R) => string
6087
valueFn: (region: R) => string
6188
}
@@ -120,9 +147,12 @@ const ConstraintTrack = <R extends GenericRegion>({
120147
>
121148
{({ scalePosition, width }: TrackProps) => (
122149
<>
123-
<TopPanel>{legend}</TopPanel>
150+
<TopPanel>
151+
<LegendWrapper>{legend}</LegendWrapper>
152+
</TopPanel>
124153
<PlotWrapper>
125154
<svg height={55} width={width}>
155+
{!allRegions && <rect x={0} y={7.5} width={width} height={1} />}
126156
{constrainedRegions.map((region: RegionWithUnclamped<R>) => {
127157
const startX = scalePosition(region.start)
128158
const stopX = scalePosition(region.stop)
@@ -139,7 +169,7 @@ const ConstraintTrack = <R extends GenericRegion>({
139169
<g>
140170
<rect
141171
x={startX}
142-
y={0}
172+
y={1}
143173
width={regionWidth}
144174
height={15}
145175
fill={colorFn(region)}
@@ -149,9 +179,9 @@ const ConstraintTrack = <R extends GenericRegion>({
149179
</TooltipAnchor>
150180
)
151181
})}
152-
<g transform="translate(0,20)">
153-
{allRegions &&
154-
allRegions.map((region: R, index: number) => {
182+
{allRegions && (
183+
<g transform="translate(0,20)">
184+
{allRegions.map((region: R, index: number) => {
155185
const startX = scalePosition(region.start)
156186
const stopX = scalePosition(region.stop)
157187
const regionWidth = stopX - startX
@@ -198,7 +228,8 @@ const ConstraintTrack = <R extends GenericRegion>({
198228
</g>
199229
)
200230
})}
201-
</g>
231+
</g>
232+
)}
202233
</svg>
203234
</PlotWrapper>
204235
</>

browser/src/GenePage/GenePage.tsx

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import { Track } from '@gnomad/region-viewer'
1010
// @ts-expect-error TS(7016) FIXME: Could not find a declaration file for module '@gno... Remove this comment to see the full error message
1111
import { TranscriptPlot } from '@gnomad/track-transcripts'
1212
import { Badge, Button } from '@gnomad/ui'
13+
import MitochondrialRegionConstraintTrack, {
14+
MitochondrialConstraintRegion,
15+
} from './MitochondrialRegionConstraintTrack'
1316

1417
import {
1518
DatasetId,
@@ -72,6 +75,40 @@ import {
7275
} from '../ChartStyles'
7376
import { logButtonClick } from '../analytics'
7477

78+
type ProteinMitochondrialGeneConstraint = {
79+
exp_lof: number
80+
exp_mis: number
81+
exp_syn: number
82+
83+
obs_lof: number
84+
obs_mis: number
85+
obs_syn: number
86+
87+
oe_lof: number
88+
oe_lof_lower: number
89+
oe_lof_upper: number
90+
91+
oe_mis: number
92+
oe_mis_lower: number
93+
oe_mis_upper: number
94+
95+
oe_syn: number
96+
oe_syn_lower: number
97+
oe_syn_upper: number
98+
}
99+
100+
type RNAMitochondrialGeneConstraint = {
101+
observed: number
102+
expected: number
103+
oe: number
104+
oe_upper: number
105+
oe_lower: number
106+
}
107+
108+
type MitochondrialGeneConstraint =
109+
| ProteinMitochondrialGeneConstraint
110+
| RNAMitochondrialGeneConstraint
111+
75112
export type Strand = '+' | '-'
76113

77114
export type GeneMetadata = {
@@ -136,6 +173,8 @@ export type Gene = GeneMetadata & {
136173
clinvar_variants: ClinvarVariant[]
137174
homozygous_variant_cooccurrence_counts: HomozygousVariantCooccurrenceCountsPerSeverityAndAf
138175
heterozygous_variant_cooccurrence_counts: HeterozygousVariantCooccurrenceCountsPerSeverityAndAf
176+
mitochondrial_constraint: MitochondrialGeneConstraint | null
177+
mitochondrial_missense_constraint_regions: MitochondrialConstraintRegion[] | null
139178
}
140179

141180
const GeneName = styled.span`
@@ -528,6 +567,13 @@ const GenePage = ({ datasetId, gene, geneId }: Props) => {
528567
</TrackWrapper>
529568
)}
530569

570+
{gene.chrom.startsWith('M') && (
571+
<MitochondrialRegionConstraintTrack
572+
constraintRegions={gene.mitochondrial_missense_constraint_regions}
573+
exons={gene.exons}
574+
/>
575+
)}
576+
531577
{hasCodingExons && gene.chrom !== 'M' && gene.pext && (
532578
<TissueExpressionTrack
533579
exons={cdsCompositeExons}

browser/src/GenePage/GenePageContainer.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,41 @@ query ${operationName}($geneId: String, $geneSymbol: String, $referenceGenome: R
260260
hom_total
261261
}
262262
}
263+
mitochondrial_constraint {
264+
...on ProteinMitochondrialGeneConstraint {
265+
exp_lof
266+
exp_mis
267+
exp_syn
268+
obs_lof
269+
obs_mis
270+
obs_syn
271+
oe_lof
272+
oe_lof_lower
273+
oe_lof_upper
274+
oe_mis
275+
oe_mis_lower
276+
oe_mis_upper
277+
oe_syn
278+
oe_syn_lower
279+
oe_syn_upper
280+
}
281+
282+
...on RNAMitochondrialGeneConstraint {
283+
observed
284+
expected
285+
oe
286+
oe_upper
287+
oe_lower
288+
}
289+
}
290+
291+
mitochondrial_missense_constraint_regions{
292+
start
293+
stop
294+
oe
295+
oe_upper
296+
oe_lower
297+
}
263298
}
264299
}
265300
`
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import React from 'react'
2+
import renderer from 'react-test-renderer'
3+
import { expect, test } from '@jest/globals'
4+
import MitochondrialRegionConstraintTrack, {
5+
MitochondrialConstraintRegion,
6+
} from './MitochondrialRegionConstraintTrack'
7+
import { Exon } from '../TranscriptPage/TranscriptPage'
8+
// @ts-expect-error
9+
import { RegionViewerContext } from '@gnomad/region-viewer'
10+
11+
const childProps = {
12+
centerPanelWidth: 3,
13+
isPositionDefined: true,
14+
leftPanelWidth: 4,
15+
regions: [],
16+
rightPanelWidth: 5,
17+
scalePosition: (i: number) => i,
18+
}
19+
20+
const exons: Exon[] = [
21+
{ feature_type: 'CDS', start: 123, stop: 234 },
22+
{ feature_type: 'UTR', start: 235, stop: 999 },
23+
{ feature_type: 'CDS', start: 1000, stop: 1999 },
24+
]
25+
26+
test('track has no unexpected changes when gene has constraint', () => {
27+
const constraintRegions: MitochondrialConstraintRegion[] = [
28+
{ start: 555, stop: 666, oe: 0.45, oe_lower: 0.37, oe_upper: 0.47 },
29+
{ start: 777, stop: 888, oe: 0.56, oe_lower: 0.52, oe_upper: 0.59 },
30+
]
31+
const tree = renderer.create(
32+
<RegionViewerContext.Provider value={childProps}>
33+
<MitochondrialRegionConstraintTrack constraintRegions={constraintRegions} exons={exons} />
34+
</RegionViewerContext.Provider>
35+
)
36+
expect(tree).toMatchSnapshot()
37+
})
38+
39+
test('track has no unexpected changes when no constraint for gene', () => {
40+
const tree = renderer.create(
41+
<RegionViewerContext.Provider value={childProps}>
42+
<MitochondrialRegionConstraintTrack constraintRegions={null} exons={exons} />
43+
</RegionViewerContext.Provider>
44+
)
45+
expect(tree).toMatchSnapshot()
46+
})
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import React from 'react'
2+
import { Exon } from '../TranscriptPage/TranscriptPage'
3+
import ConstraintTrack, { regionsInExons, RegionAttributeList } from '../ConstraintTrack'
4+
5+
export type MitochondrialConstraintRegion = {
6+
start: number
7+
stop: number
8+
oe: number
9+
oe_upper: number
10+
oe_lower: number
11+
}
12+
13+
type Props = {
14+
constraintRegions: MitochondrialConstraintRegion[] | null
15+
exons: Exon[]
16+
}
17+
18+
const constraintColor = '#fd8d3c'
19+
20+
const Legend = () => (
21+
<>
22+
<span>Constrained region</span>
23+
<svg width={50} height={25}>
24+
<rect x={10} y={3} width={30} height={10} stroke="#000" fill={constraintColor} />
25+
</svg>
26+
</>
27+
)
28+
29+
type TooltipProps = {
30+
region: MitochondrialConstraintRegion
31+
}
32+
33+
const Tooltip = ({ region }: TooltipProps) => {
34+
return (
35+
<RegionAttributeList>
36+
<div>
37+
<dt>Coordinates:</dt>
38+
<dd>{`M:${region.start}-${region.stop}`}</dd>
39+
</div>
40+
<div>
41+
<dt>Missense observed/expected:</dt>
42+
<dd>
43+
{region.oe.toFixed(3)} ({region.oe_lower.toFixed(3)}-{region.oe_upper.toFixed(3)})
44+
</dd>
45+
</div>
46+
</RegionAttributeList>
47+
)
48+
}
49+
50+
const formattedOE = (region: MitochondrialConstraintRegion) => region.oe.toFixed(3)
51+
52+
const MitochondrialConstraintRegionTrack = ({ constraintRegions, exons }: Props) => {
53+
if (constraintRegions === null) {
54+
return null
55+
}
56+
57+
return (
58+
<ConstraintTrack
59+
trackTitle="Regional constraint"
60+
infobuttonTopic="TK-mitochondrial-gene-constraint"
61+
legend={<Legend />}
62+
valueFn={formattedOE}
63+
colorFn={() => constraintColor}
64+
tooltipComponent={Tooltip}
65+
allRegions={null}
66+
constrainedRegions={regionsInExons(
67+
constraintRegions,
68+
exons.filter((exon) => exon.feature_type === 'CDS')
69+
)}
70+
/>
71+
)
72+
}
73+
74+
export default MitochondrialConstraintRegionTrack

0 commit comments

Comments
 (0)