Skip to content

Commit

Permalink
Add column resizers to the variant ANN/CSQ panels (#3698)
Browse files Browse the repository at this point in the history
  • Loading branch information
cmdcolin committed May 18, 2023
1 parent 21b9b55 commit 4c9a930
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 35 deletions.
Expand Up @@ -590,6 +590,7 @@ export function FeatureDetails(props: {
feature: SimpleFeatureSerialized
depth?: number
omit?: string[]
descriptions?: Record<string, React.ReactNode>
formatter?: (val: unknown, key: string) => React.ReactNode
}) {
const { omit = [], model, feature, depth = 0 } = props
Expand Down
33 changes: 16 additions & 17 deletions plugins/variants/src/VariantFeatureWidget/AnnotGrid.tsx
Expand Up @@ -6,6 +6,8 @@ import {
GridValidRowModel,
} from '@mui/x-data-grid'
import { Checkbox, FormControlLabel, Typography } from '@mui/material'
import ResizeBar, { useResizeBar } from '@jbrowse/core/ui/ResizeBar'
import { measureGridWidth } from '@jbrowse/core/util'

export default function VariantAnnotPanel({
rows,
Expand All @@ -14,17 +16,14 @@ export default function VariantAnnotPanel({
rows: GridValidRowModel[]
columns: GridColDef[]
}) {
const rowHeight = 25
const headerHeight = 100
const footerHeight = 50
const { ref, scrollLeft } = useResizeBar()
const [checked, setChecked] = useState(false)
const height =
Math.min(rows.length, 100) * rowHeight +
headerHeight +
(checked ? 50 : 0) +
footerHeight
const [widths, setWidths] = useState(
columns.map(e => measureGridWidth(rows.map(r => r[e.field]))),
)

return rows.length ? (
<div>
<div ref={ref}>
<FormControlLabel
control={
<Checkbox
Expand All @@ -34,16 +33,16 @@ export default function VariantAnnotPanel({
}
label={<Typography variant="body2">Show options</Typography>}
/>
<div
style={{
height,
width: '100%',
}}
>
<div ref={ref}>
<ResizeBar
widths={widths}
setWidths={setWidths}
scrollLeft={scrollLeft}
/>
<DataGrid
rowHeight={rowHeight}
rowHeight={25}
rows={rows}
columns={columns}
columns={columns.map((c, i) => ({ ...c, width: widths[i] }))}
slots={{ toolbar: checked ? GridToolbar : null }}
/>
</div>
Expand Down
@@ -0,0 +1,27 @@
import React from 'react'
import { BaseCard } from '@jbrowse/core/BaseFeatureWidget/BaseFeatureDetail'
import AnnotGrid from './AnnotGrid'

export default function VariantAnnotationTable({
data,
fields,
title,
}: {
data: string[]
fields: string[]
title: string
}) {
return data.length ? (
<BaseCard title={title}>
<AnnotGrid
rows={
data.map((elt, id) => ({
id,
...Object.fromEntries(elt.split('|').map((e, i) => [fields[i], e])),
})) || []
}
columns={fields.map(c => ({ field: c }))}
/>
</BaseCard>
) : null
}
77 changes: 61 additions & 16 deletions plugins/variants/src/VariantFeatureWidget/VariantFeatureWidget.tsx
@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import React from 'react'
import { observer } from 'mobx-react'
import { Divider, Paper } from '@mui/material'
Expand All @@ -7,25 +6,31 @@ import { parseBreakend } from '@gmod/vcf'

// locals
import VariantSampleGrid from './VariantSampleGrid'
import VariantCsqPanel from './VariantCsqPanel'
import VariantAnnPanel from './VariantAnnPanel'
import BreakendPanel from './BreakendPanel'
import VariantAnnotationTable from './VariantAnnotationTable'
import { SimpleFeatureSerialized } from '@jbrowse/core/util'

function VariantFeatureDetails(props: any) {
const basicDescriptions = {
CHROM: 'chromosome: An identifier from the reference genome',
POS: 'position: The reference position, with the 1st base having position 1',
ID: 'identifier: Semi-colon separated list of unique identifiers where available',
REF: 'reference base(s): Each base must be one of A,C,G,T,N (case insensitive).',
ALT: 'alternate base(s): Comma-separated list of alternate non-reference alleles',
QUAL: 'quality: Phred-scaled quality score for the assertion made in ALT',
FILTER:
'filter status: PASS if this position has passed all filters, otherwise a semicolon-separated list of codes for filters that fail',
}

function VariantFeatureDetails(props: {
model: {
featureData: SimpleFeatureSerialized
descriptions: Record<string, string>
}
}) {
const { model } = props
const { featureData, descriptions } = model
const feat = JSON.parse(JSON.stringify(featureData))
const { samples, ...rest } = feat
const basicDescriptions = {
CHROM: 'chromosome: An identifier from the reference genome',
POS: 'position: The reference position, with the 1st base having position 1',
ID: 'identifier: Semi-colon separated list of unique identifiers where available',
REF: 'reference base(s): Each base must be one of A,C,G,T,N (case insensitive).',
ALT: 'alternate base(s): Comma-separated list of alternate non-reference alleles',
QUAL: 'quality: Phred-scaled quality score for the assertion made in ALT',
FILTER:
'filter status: PASS if this position has passed all filters, otherwise a semicolon-separated list of codes for filters that fail',
}

return (
<Paper data-testid="variant-side-drawer">
Expand All @@ -35,9 +40,9 @@ function VariantFeatureDetails(props: any) {
{...props}
/>
<Divider />
<VariantCsqPanel feature={rest} descriptions={descriptions} />
<CsqPanel feature={rest} descriptions={descriptions} />
<Divider />
<VariantAnnPanel feature={rest} descriptions={descriptions} />
<AnnPanel feature={rest} descriptions={descriptions} />
<Divider />
{feat.type === 'breakend' ? (
<BreakendPanel
Expand All @@ -64,4 +69,44 @@ function VariantFeatureDetails(props: any) {
)
}

function AnnPanel({
descriptions,
feature,
}: {
descriptions: { INFO?: { ANN?: { Description?: string } } }
feature: { INFO?: { ANN?: string[] } }
}) {
const annDesc = descriptions?.INFO?.ANN?.Description
const annFields =
annDesc?.match(/.*Functional annotations:'(.*)'$/)?.[1].split('|') || []
const ann = feature.INFO?.ANN || []
return (
<VariantAnnotationTable
fields={annFields}
data={ann}
title="Variant ANN field"
/>
)
}

function CsqPanel({
descriptions,
feature,
}: {
descriptions: { INFO?: { CSQ?: { Description?: string } } }
feature: { INFO?: { CSQ?: string[] } }
}) {
const csqDescription = descriptions?.INFO?.CSQ?.Description
const csqFields =
csqDescription?.match(/.*Format: (.*)/)?.[1].split('|') || []
const csq = (feature.INFO?.CSQ || []) as string[]
return (
<VariantAnnotationTable
fields={csqFields}
data={csq}
title="Variant CSQ field"
/>
)
}

export default observer(VariantFeatureDetails)
Expand Up @@ -54,8 +54,7 @@ function SampleFilters({

export default function VariantSamples(props: {
feature: SimpleFeatureSerialized
// eslint-disable-next-line @typescript-eslint/no-explicit-any
descriptions: any
descriptions: { FORMAT?: { [key: string]: { Description?: string } } }
}) {
const { feature, descriptions = {} } = props
const { ref, scrollLeft } = useResizeBar()
Expand Down

0 comments on commit 4c9a930

Please sign in to comment.