Skip to content

Commit

Permalink
feat(components): mutation filter: add explanation to info box
Browse files Browse the repository at this point in the history
closes #182
  • Loading branch information
fengelniederhammer committed Jul 15, 2024
1 parent 530ab9e commit 361a519
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 6 deletions.
2 changes: 2 additions & 0 deletions components/src/lapisApi/ReferenceGenome.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,5 @@ export const getSegmentNames = (referenceGenome: ReferenceGenome, sequenceType:
}
}
};

export const isSingleSegmented = (referenceGenome: ReferenceGenome) => referenceGenome.nucleotideSequences.length === 1;
117 changes: 117 additions & 0 deletions components/src/preact/mutationFilter/mutation-filter-info.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { useContext } from 'preact/hooks';

import { isSingleSegmented } from '../../lapisApi/ReferenceGenome';
import { ReferenceGenomeContext } from '../ReferenceGenomeContext';
import Info, { InfoHeadline1, InfoHeadline2, InfoParagraph } from '../components/info';

export const MutationFilterInfo = () => {
const referenceGenome = useContext(ReferenceGenomeContext);

const firstGene = referenceGenome.genes[0].name;
return (
<Info height={'80vh'}>
<InfoHeadline1> Mutation Filter</InfoHeadline1>
<InfoParagraph>This component allows you to filter for mutations at specific positions.</InfoParagraph>

<InfoHeadline2> Nucleotide Mutations and Insertions</InfoHeadline2>
{isSingleSegmented(referenceGenome) ? (
<SingleSegmentedNucleotideMutationsInfo />
) : (
<MultiSegmentedNucleotideMutationsInfo />
)}

<InfoHeadline2>Amino Acid Mutations and Insertions</InfoHeadline2>
<InfoParagraph>
An amino acid mutation has the format <b>&lt;gene&gt;:&lt;position&gt;&lt;base&gt;</b> or
<b>&lt;gene&gt;:&lt;base_ref&gt;&lt;position&gt;&lt;base&gt;</b>. A <b>&lt;base&gt;</b> can be one of
the 20 amino acid codes. It can also be <b>-</b> for deletion and <b>X</b> for unknown. Example:{' '}
<b>E:57Q</b>.
</InfoParagraph>
<InfoParagraph>
Insertions can be searched for in the same manner, they just need to have <b>ins_</b> appended to the
start of the mutation. Example: <b>ins_{firstGene}:31:N</b> would filter for sequences with an insertion
of N between positions 31 and 32 in the gene {firstGene}.
</InfoParagraph>
<InfoParagraph>
This organism has the following genes: {referenceGenome.genes.map((gene) => gene.name).join(', ')}.
</InfoParagraph>

<InfoHeadline2>Insertion Wildcards</InfoHeadline2>
<InfoParagraph>
This component supports insertion queries that contain wildcards <b>?</b>. For example{' '}
<b>ins_{firstGene}:214:?EP?</b> will match all cases where segment <b>{firstGene}</b> has an insertion
of <b>EP</b> between the positions <b>214</b> and <b>215</b> but also an insertion of other amino acids
which include the <b>EP</b>, e.g. the insertion <b>EPE</b> will be matched.
</InfoParagraph>
<InfoParagraph>
You can also use wildcards to match any insertion at a given position. For example{' '}
<b>ins_{firstGene}:214:?</b> match any (but at least one) insertion between the positions 214 and 215.
</InfoParagraph>

<InfoHeadline2>Multiple Mutations</InfoHeadline2>
<InfoParagraph>
Multiple mutation filters can be provided by adding one mutation after the other.
</InfoParagraph>

<InfoHeadline2>Any Mutation</InfoHeadline2>
<InfoParagraph>
To filter for any mutation at a given position you can omit the <b>&lt;base&gt;</b>. Example:{' '}
<b>{firstGene}:20</b>.
</InfoParagraph>

<InfoHeadline2>No Mutation</InfoHeadline2>
<InfoParagraph>
You can write a <b>.</b> for the <b>&lt;base&gt;</b> to filter for sequences for which it is confirmed
that no mutation occurred, i.e. has the same base as the reference genome at the specified position.
</InfoParagraph>
</Info>
);
};

const SingleSegmentedNucleotideMutationsInfo = () => {
return (
<>
<InfoParagraph>
This organism is single-segmented. Thus, nucleotide mutations have the format{' '}
<b>&lt;position&gt;&lt;base&gt;</b> or <b>&lt;base_ref&gt;&lt;position&gt;&lt;base&gt;</b>. The{' '}
<b>&lt;base_ref&gt;</b> is the reference base at the position. It is optional. A <b>&lt;base&gt;</b> can
be one of the four nucleotides <b>A</b>, <b>T</b>, <b>C</b>, and <b>G</b>. It can also be <b>-</b> for
deletion and <b>N</b> for unknown. For example if the reference sequence is <b>A</b> at position{' '}
<b>23</b> both: <b>23T</b> and <b>A23T</b> will yield the same results.
</InfoParagraph>
<InfoParagraph>
Insertions can be searched for in the same manner, they just need to have <b>ins_</b> appended to the
start of the mutation. Example: <b>ins_1046:A</b> would filter for sequences with an insertion of A
between the positions 1046 and 1047 in the nucleotide sequence.
</InfoParagraph>
</>
);
};

const MultiSegmentedNucleotideMutationsInfo = () => {
const referenceGenome = useContext(ReferenceGenomeContext);

const firstSegment = referenceGenome.nucleotideSequences[0].name;

return (
<>
<InfoParagraph>
This organism is multi-segmented. Thus, nucleotide mutations have the format{' '}
<b>&lt;segment&gt;:&lt;position&gt;&lt;base&gt;</b> or{' '}
<b>&lt;segment&gt;:&lt;base_ref&gt;&lt;position&gt;&lt;base&gt;</b>. <b>&lt;base_ref&gt;</b> is the
reference base at the position. It is optional. A <b>&lt;base&gt;</b> can be one of the four nucleotides{' '}
<b>A</b>, <b>T</b>, <b>C</b>, and <b>G</b>. It can also be <b>-</b> for deletion and <b>N</b> for
unknown. For example if the reference sequence is <b>A</b> at position <b>23</b> both:{' '}
<b>{firstSegment}:23T</b> and <b>{firstSegment}:A23T</b> will yield the same results.
</InfoParagraph>
<InfoParagraph>
Insertions can be searched for in the same manner, they just need to have <b>ins_</b> appended to the
start of the mutation. Example: <b>ins_{firstSegment}:10462:A</b>.
</InfoParagraph>
<InfoParagraph>
This organism has the following segments:{' '}
{referenceGenome.nucleotideSequences.map((gene) => gene.name).join(', ')}.
</InfoParagraph>{' '}
</>
);
};
4 changes: 2 additions & 2 deletions components/src/preact/mutationFilter/mutation-filter.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { type FunctionComponent } from 'preact';
import { useContext, useRef, useState } from 'preact/hooks';

import { MutationFilterInfo } from './mutation-filter-info';
import { parseAndValidateMutation } from './parseAndValidateMutation';
import { type ReferenceGenome } from '../../lapisApi/ReferenceGenome';
import { type Deletion, type Insertion, type Mutation, type Substitution } from '../../utils/mutations';
import { ReferenceGenomeContext } from '../ReferenceGenomeContext';
import { ErrorBoundary } from '../components/error-boundary';
import Info from '../components/info';
import { singleGraphColorRGBByName } from '../shared/charts/colors';
import { DeleteIcon } from '../shared/icons/DeleteIcon';

Expand Down Expand Up @@ -103,7 +103,7 @@ export const MutationFilterInner: FunctionComponent<MutationFilterInnerProps> =
return (
<form className='w-full border boder-gray-300 rounded-md relative' onSubmit={handleSubmit} ref={formRef}>
<div className='absolute -top-3 -right-3'>
<Info height={'100px'}>Info for mutation filter</Info>
<MutationFilterInfo />
</div>
<div className='w-full flex p-2 flex-wrap items-center'>
<SelectedMutationDisplay
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ describe('getSequenceType', () => {
],
};

it('should return nucleotide when the segment is undefined for singe segmented genome', () => {
it('should return nucleotide when the segment is undefined for single segmented genome', () => {
expect(sequenceTypeFromSegment('nuc1', singleSegmentedReferenceGenome)).toBe('nucleotide');
expect(sequenceTypeFromSegment(undefined, singleSegmentedReferenceGenome)).toBe('nucleotide');
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type { ReferenceGenome } from '../../lapisApi/ReferenceGenome';
import { isSingleSegmented, type ReferenceGenome } from '../../lapisApi/ReferenceGenome';
import type { SequenceType } from '../../types';

export const sequenceTypeFromSegment = (
possibleSegment: string | undefined,
referenceGenome: ReferenceGenome,
): SequenceType | undefined => {
if (possibleSegment === undefined) {
return referenceGenome.nucleotideSequences.length === 1 ? 'nucleotide' : undefined;
return isSingleSegmented(referenceGenome) ? 'nucleotide' : undefined;
}

if (referenceGenome.nucleotideSequences.some((sequence) => sequence.name === possibleSegment)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { Meta, StoryObj } from '@storybook/web-components';
import { html } from 'lit';

import { withComponentDocs } from '../../../.storybook/ComponentDocsBlock';
import { LAPIS_URL } from '../../constants';
import { LAPIS_URL, REFERENCE_GENOME_ENDPOINT } from '../../constants';
import '../app';
import { type MutationFilterProps } from '../../preact/mutationFilter/mutation-filter';
import { withinShadowRoot } from '../withinShadowRoot.story';
Expand Down Expand Up @@ -128,3 +128,59 @@ export const FiresFilterOnBlurEvent: StoryObj<MutationFilterProps> = {
});
},
};

export const MultiSegmentedReferenceGenomes: StoryObj<MutationFilterProps> = {
...Template,
args: {
initialValue: ['seg1:123T', 'gene2:56', 'ins_seg2:78:AAA'],
},
parameters: {
fetchMock: {
mocks: [
{
matcher: {
name: 'referenceGenome',
url: REFERENCE_GENOME_ENDPOINT,
},
response: {
status: 200,
body: {
nucleotideSequences: [
{
name: 'seg1',
sequence: 'dummy',
},
{
name: 'seg2',
sequence: 'dummy',
},
],
genes: [
{
name: 'gene1',
sequence: 'dummy',
},
{
name: 'gene2',
sequence: 'dummy',
},
],
},
},
options: {
overwriteRoutes: false,
},
},
],
},
},
play: async ({ canvasElement }) => {
const canvas = await withinShadowRoot(canvasElement, 'gs-mutation-filter');

await waitFor(() => {
expect(canvas.getByText('seg1:123T')).toBeVisible();
expect(canvas.getByText('gene2:56')).toBeVisible();
return expect(canvas.getByText('ins_seg2:78:AAA')).toBeVisible();
});
},
};

0 comments on commit 361a519

Please sign in to comment.