Skip to content

Commit

Permalink
feat(website): show deletions as ranges (#835)
Browse files Browse the repository at this point in the history
  • Loading branch information
chaoran-chen authored Jan 25, 2024
1 parent 8f35fa6 commit 4cdda5d
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 21 deletions.
30 changes: 18 additions & 12 deletions website/src/components/SequenceDetailsPage/getTableData.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,22 +156,22 @@ describe('getTableData', () => {
expect(data).toContainEqual({
label: 'Nucleotide substitutions',
name: 'nucleotideSubstitutions',
value: 'nucleotideMutation1, nucleotideMutation2',
value: 'T10A, C30G',
});
expect(data).toContainEqual({
label: 'Nucleotide deletions',
name: 'nucleotideDeletions',
value: 'nucleotideDeletion1-, nucleotideDeletion2-',
value: '20, 21, 40-42',
});
expect(data).toContainEqual({
label: 'Amino acid substitutions',
name: 'aminoAcidSubstitutions',
value: 'aminoAcidMutation1, aminoAcidMutation2',
value: 'gene1:N10Y, gene1:T30N',
});
expect(data).toContainEqual({
label: 'Amino acid deletions',
name: 'aminoAcidDeletions',
value: 'aminoAcidDeletion1-, aminoAcidDeletion2-',
value: 'gene1:20-23, gene1:40',
});
});

Expand Down Expand Up @@ -209,16 +209,22 @@ describe('getTableData', () => {
});

const nucleotideMutations = [
{ count: 0, proportion: 0, mutation: 'nucleotideMutation1' },
{ count: 0, proportion: 0, mutation: 'nucleotideDeletion1-' },
{ count: 0, proportion: 0, mutation: 'nucleotideMutation2' },
{ count: 0, proportion: 0, mutation: 'nucleotideDeletion2-' },
{ count: 0, proportion: 0, mutation: 'T10A' },
{ count: 0, proportion: 0, mutation: 'A20-' },
{ count: 0, proportion: 0, mutation: 'A21-' },
{ count: 0, proportion: 0, mutation: 'C30G' },
{ count: 0, proportion: 0, mutation: 'G40-' },
{ count: 0, proportion: 0, mutation: 'C41-' },
{ count: 0, proportion: 0, mutation: 'T42-' },
];
const aminoAcidMutations = [
{ count: 0, proportion: 0, mutation: 'aminoAcidMutation1' },
{ count: 0, proportion: 0, mutation: 'aminoAcidDeletion1-' },
{ count: 0, proportion: 0, mutation: 'aminoAcidMutation2' },
{ count: 0, proportion: 0, mutation: 'aminoAcidDeletion2-' },
{ count: 0, proportion: 0, mutation: 'gene1:N10Y' },
{ count: 0, proportion: 0, mutation: 'gene1:R20-' },
{ count: 0, proportion: 0, mutation: 'gene1:R21-' },
{ count: 0, proportion: 0, mutation: 'gene1:N22-' },
{ count: 0, proportion: 0, mutation: 'gene1:P23-' },
{ count: 0, proportion: 0, mutation: 'gene1:T30N' },
{ count: 0, proportion: 0, mutation: 'gene1:F40-' },
];

const nucleotideInsertions = [
Expand Down
67 changes: 58 additions & 9 deletions website/src/components/SequenceDetailsPage/getTableData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,12 @@ function toTableData(config: Schema) {
{
label: 'Nucleotide substitutions',
name: 'nucleotideSubstitutions',
value: mutationsToCommaSeparatedString(nucleotideMutations, (m) => !m.endsWith('-')),
value: substitutionsToCommaSeparatedString(nucleotideMutations),
},
{
label: 'Nucleotide deletions',
name: 'nucleotideDeletions',
value: mutationsToCommaSeparatedString(nucleotideMutations, (m) => m.endsWith('-')),
value: deletionsToCommaSeparatedString(nucleotideMutations),
},
{
label: 'Nucleotide insertions',
Expand All @@ -121,12 +121,12 @@ function toTableData(config: Schema) {
{
label: 'Amino acid substitutions',
name: 'aminoAcidSubstitutions',
value: mutationsToCommaSeparatedString(aminoAcidMutations, (m) => !m.endsWith('-')),
value: substitutionsToCommaSeparatedString(aminoAcidMutations),
},
{
label: 'Amino acid deletions',
name: 'aminoAcidDeletions',
value: mutationsToCommaSeparatedString(aminoAcidMutations, (m) => m.endsWith('-')),
value: deletionsToCommaSeparatedString(aminoAcidMutations),
},
{
label: 'Amino acid insertions',
Expand All @@ -151,13 +151,62 @@ function mapValueToDisplayedValue(value: undefined | null | string | number, met
return value;
}

function mutationsToCommaSeparatedString(
mutationData: MutationProportionCount[],
filter: (mutation: string) => boolean,
) {
function substitutionsToCommaSeparatedString(mutationData: MutationProportionCount[]) {
return mutationData
.map((m) => m.mutation)
.filter(filter)
.filter((m) => !m.endsWith('-'))
.join(', ');
}

function deletionsToCommaSeparatedString(mutationData: MutationProportionCount[]) {
const segmentPositions = new Map<string | undefined, number[]>();
mutationData
.filter((m) => m.mutation.endsWith('-'))
.forEach((m) => {
const parts = m.mutation.split(':');
const [segment, mutation] = parts.length === 1 ? ([undefined, parts[0]] as const) : parts;
const position = Number.parseInt(mutation.slice(1, -1), 10);
if (!segmentPositions.has(segment)) {
segmentPositions.set(segment, []);
}
segmentPositions.get(segment)!.push(position);
});
const segmentRanges = [...segmentPositions.entries()].map(([segment, positions]) => {
const sortedPositions = positions.sort();
const ranges = [];
let rangeStart: number | null = null;
for (let i = 0; i < sortedPositions.length; i++) {
const current = sortedPositions[i];
const next = sortedPositions[i + 1] as number | undefined;
if (rangeStart === null) {
rangeStart = current;
}
if (next === undefined || next !== current + 1) {
if (current - rangeStart >= 2) {
ranges.push(`${rangeStart}-${current}`);
} else {
ranges.push(rangeStart.toString());
if (current !== rangeStart) {
ranges.push(current.toString());
}
}
rangeStart = null;
}
}
return { segment, ranges };
});
segmentRanges.sort((a, b) => {
const safeA = a.segment ?? '';
const safeB = b.segment ?? '';
if (safeA <= safeB) {
return -1;
} else {
return 1;
}
});
return segmentRanges
.map(({ segment, ranges }) => ranges.map((range) => `${segment !== undefined ? segment + ':' : ''}${range}`))
.flat()
.join(', ');
}

Expand Down

0 comments on commit 4cdda5d

Please sign in to comment.