Skip to content

Commit

Permalink
feat(web): display relative substitutions in nuc sequence view
Browse files Browse the repository at this point in the history
  • Loading branch information
ivan-aksamentov committed May 14, 2024
1 parent 6e7afc4 commit 4045383
Show file tree
Hide file tree
Showing 4 changed files with 334 additions and 152 deletions.
170 changes: 18 additions & 152 deletions packages/nextclade-web/src/components/SequenceView/SequenceView.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
import React from 'react'
import React, { useMemo } from 'react'
import { ReactResizeDetectorDimensions, withResizeDetector } from 'react-resize-detector'
import { useRecoilValue } from 'recoil'
import { SequenceMarkerAmbiguous } from 'src/components/SequenceView/SequenceMarkerAmbiguous'
import { useTranslationSafe } from 'src/helpers/useTranslationSafe'
import { maxNucMarkersAtom } from 'src/state/seqViewSettings.state'
import styled from 'styled-components'

import type { AnalysisResult } from 'src/types'
import { genomeSizeAtom } from 'src/state/results.state'
import { SequenceMarkerGap } from './SequenceMarkerGap'
import { SequenceMarkerMissing } from './SequenceMarkerMissing'
import { SequenceMarkerMutation } from './SequenceMarkerMutation'
import { SequenceMarkerUnsequencedEnd, SequenceMarkerUnsequencedStart } from './SequenceMarkerUnsequenced'
import { SequenceMarkerFrameShift } from './SequenceMarkerFrameShift'
import { SequenceMarkerInsertion } from './SequenceMarkerInsertion'
import { currentRefNodeNameAtom } from 'src/state/results.state'
import { SequenceViewAbsolute } from './SequenceViewAbsolute'
import { SequenceViewRelative } from './SequenceViewRelative'

export const SequenceViewWrapper = styled.div`
display: flex;
Expand All @@ -31,158 +23,32 @@ export const SequenceViewSVG = styled.svg`
height: 100%;
`

export const SequenceViewText = styled.p`
margin: auto;
`

export interface SequenceViewProps extends ReactResizeDetectorDimensions {
sequence: AnalysisResult
}

export function SequenceViewUnsized({ sequence, width }: SequenceViewProps) {
const {
index,
seqName,
substitutions,
missing,
deletions,
alignmentRange,
frameShifts,
insertions,
nucToAaMuts,
nonACGTNs,
} = sequence

const { t } = useTranslationSafe()
const maxNucMarkers = useRecoilValue(maxNucMarkersAtom)

const genomeSize = useRecoilValue(genomeSizeAtom)

if (!width) {
return (
<SequenceViewWrapper>
<SequenceViewSVG fill="transparent" viewBox={`0 0 10 10`} />
</SequenceViewWrapper>
)
}

const pixelsPerBase = width / genomeSize

const mutationViews = substitutions.map((substitution) => {
return (
<SequenceMarkerMutation
key={substitution.pos}
index={index}
seqName={seqName}
substitution={substitution}
nucToAaMuts={nucToAaMuts}
pixelsPerBase={pixelsPerBase}
/>
)
})

const missingViews = missing.map((oneMissing) => {
return (
<SequenceMarkerMissing
key={oneMissing.range.begin}
index={index}
seqName={seqName}
missing={oneMissing}
pixelsPerBase={pixelsPerBase}
/>
)
})

const ambigViews = nonACGTNs.map((ambig) => {
return (
<SequenceMarkerAmbiguous
key={ambig.range.begin}
index={index}
seqName={seqName}
ambiguous={ambig}
pixelsPerBase={pixelsPerBase}
/>
)
})
const refNodeName = useRecoilValue(currentRefNodeNameAtom)

const deletionViews = deletions.map((deletion) => {
return (
<SequenceMarkerGap
key={deletion.range.begin}
index={index}
seqName={seqName}
deletion={deletion}
nucToAaMuts={nucToAaMuts}
pixelsPerBase={pixelsPerBase}
/>
)
})
const view = useMemo(() => {
if (!width) {
return null
}

const insertionViews = insertions.map((insertion) => {
return (
<SequenceMarkerInsertion
key={insertion.pos}
index={index}
seqName={seqName}
insertion={insertion}
pixelsPerBase={pixelsPerBase}
/>
)
})
if (refNodeName === '_root') {
return <SequenceViewAbsolute sequence={sequence} width={width} />
}

const frameShiftMarkers = frameShifts.map((frameShift) => (
<SequenceMarkerFrameShift
key={`${frameShift.cdsName}_${frameShift.nucAbs.map((na) => na.begin).join('-')}`}
index={index}
seqName={seqName}
frameShift={frameShift}
pixelsPerBase={pixelsPerBase}
/>
))
if (refNodeName === '_parent') {
return <SequenceViewRelative sequence={sequence} width={width} refNodeName={refNodeName} />
}

const totalMarkers =
mutationViews.length + deletionViews.length + missingViews.length + frameShiftMarkers.length + insertionViews.length
if (totalMarkers > maxNucMarkers) {
return (
<SequenceViewWrapper>
<SequenceViewText
title={t(
"Markers are the colored rectangles which represent mutations, deletions etc. There is a technical limit of how many of those can be displayed at a time, depending on how fast your computer is. You can tune the threshold in the 'Settings' dialog, accessible with the button on the top panel.",
)}
>
{t(
'Too many markers to display ({{totalMarkers}}). The threshold ({{maxNucMarkers}}) can be increased in "Settings" dialog',
{ totalMarkers, maxNucMarkers },
)}
</SequenceViewText>
</SequenceViewWrapper>
)
}
return <SequenceViewRelative sequence={sequence} width={width} refNodeName={refNodeName} />
}, [refNodeName, sequence, width])

return (
<SequenceViewWrapper>
<SequenceViewSVG viewBox={`0 0 ${width} 10`}>
<rect fill="transparent" x={0} y={-10} width={genomeSize} height="30" />
<SequenceMarkerUnsequencedStart
index={index}
seqName={seqName}
alignmentStart={alignmentRange.begin}
pixelsPerBase={pixelsPerBase}
/>
{mutationViews}
{missingViews}
{ambigViews}
{deletionViews}
{insertionViews}
<SequenceMarkerUnsequencedEnd
index={index}
seqName={seqName}
genomeSize={genomeSize}
alignmentEnd={alignmentRange.end}
pixelsPerBase={pixelsPerBase}
/>
{frameShiftMarkers}
</SequenceViewSVG>
<SequenceViewSVG viewBox={`0 0 ${width} 10`}>{view}</SequenceViewSVG>
</SequenceViewWrapper>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import type { AnalysisResult } from 'src/types'
import React from 'react'
import { useRecoilValue } from 'recoil'
import { SequenceMarkerAmbiguous } from 'src/components/SequenceView/SequenceMarkerAmbiguous'
import { useTranslationSafe } from 'src/helpers/useTranslationSafe'
import { maxNucMarkersAtom } from 'src/state/seqViewSettings.state'
import { genomeSizeAtom } from 'src/state/results.state'
import { SequenceMarkerGap } from './SequenceMarkerGap'
import { SequenceMarkerMissing } from './SequenceMarkerMissing'
import { SequenceMarkerMutation } from './SequenceMarkerMutation'
import { SequenceMarkerUnsequencedEnd, SequenceMarkerUnsequencedStart } from './SequenceMarkerUnsequenced'
import { SequenceMarkerFrameShift } from './SequenceMarkerFrameShift'
import { SequenceMarkerInsertion } from './SequenceMarkerInsertion'

export interface SequenceViewAbsoluteProps {
sequence: AnalysisResult
width: number
}

export function SequenceViewAbsolute({ sequence, width }: SequenceViewAbsoluteProps) {
const {
index,
seqName,
substitutions,
missing,
deletions,
alignmentRange,
frameShifts,
insertions,
nucToAaMuts,
nonACGTNs,
} = sequence

const { t } = useTranslationSafe()
const maxNucMarkers = useRecoilValue(maxNucMarkersAtom)

const genomeSize = useRecoilValue(genomeSizeAtom)

const pixelsPerBase = width / genomeSize

const mutationViews = substitutions.map((substitution) => {
return (
<SequenceMarkerMutation
key={substitution.pos}
index={index}
seqName={seqName}
substitution={substitution}
nucToAaMuts={nucToAaMuts}
pixelsPerBase={pixelsPerBase}
/>
)
})

const missingViews = missing.map((oneMissing) => {
return (
<SequenceMarkerMissing
key={oneMissing.range.begin}
index={index}
seqName={seqName}
missing={oneMissing}
pixelsPerBase={pixelsPerBase}
/>
)
})

const ambigViews = nonACGTNs.map((ambig) => {
return (
<SequenceMarkerAmbiguous
key={ambig.range.begin}
index={index}
seqName={seqName}
ambiguous={ambig}
pixelsPerBase={pixelsPerBase}
/>
)
})

const deletionViews = deletions.map((deletion) => {
return (
<SequenceMarkerGap
key={deletion.range.begin}
index={index}
seqName={seqName}
deletion={deletion}
nucToAaMuts={nucToAaMuts}
pixelsPerBase={pixelsPerBase}
/>
)
})

const insertionViews = insertions.map((insertion) => {
return (
<SequenceMarkerInsertion
key={insertion.pos}
index={index}
seqName={seqName}
insertion={insertion}
pixelsPerBase={pixelsPerBase}
/>
)
})

const frameShiftMarkers = frameShifts.map((frameShift) => (
<SequenceMarkerFrameShift
key={`${frameShift.cdsName}_${frameShift.nucAbs.map((na) => na.begin).join('-')}`}
index={index}
seqName={seqName}
frameShift={frameShift}
pixelsPerBase={pixelsPerBase}
/>
))

const totalMarkers =
mutationViews.length + deletionViews.length + missingViews.length + frameShiftMarkers.length + insertionViews.length
if (totalMarkers > maxNucMarkers) {
return (
<p
title={t(
"Markers are the colored rectangles which represent mutations, deletions etc. There is a technical limit of how many of those can be displayed at a time, depending on how fast your computer is. You can tune the threshold in the 'Settings' dialog, accessible with the button on the top panel.",
)}
>
{t(
'Too many markers to display ({{totalMarkers}}). The threshold ({{maxNucMarkers}}) can be increased in "Settings" dialog',
{ totalMarkers, maxNucMarkers },
)}
</p>
)
}

return (
<>
<rect fill="transparent" x={0} y={-10} width={genomeSize} height="30" />
<SequenceMarkerUnsequencedStart
index={index}
seqName={seqName}
alignmentStart={alignmentRange.begin}
pixelsPerBase={pixelsPerBase}
/>
{mutationViews}
{missingViews}
{ambigViews}
{deletionViews}
{insertionViews}
<SequenceMarkerUnsequencedEnd
index={index}
seqName={seqName}
genomeSize={genomeSize}
alignmentEnd={alignmentRange.end}
pixelsPerBase={pixelsPerBase}
/>
{frameShiftMarkers}
</>
)
}
Loading

0 comments on commit 4045383

Please sign in to comment.