Skip to content

Commit

Permalink
feat(components): initial mutations over time component
Browse files Browse the repository at this point in the history
  • Loading branch information
JonasKellerer committed Jul 17, 2024
1 parent 956d1a7 commit 1bda5d4
Show file tree
Hide file tree
Showing 16 changed files with 894 additions and 18 deletions.
9 changes: 8 additions & 1 deletion components/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
"flatpickr": "^4.6.13",
"gridjs": "^6.2.0",
"lit": "^3.1.3",
"object-hash": "^3.0.0",
"preact": "^10.20.1",
"zod": "^3.23.0"
},
Expand All @@ -87,6 +88,7 @@
"@storybook/web-components": "^8.0.9",
"@storybook/web-components-vite": "^8.0.9",
"@types/node": "^20.12.7",
"@types/object-hash": "^3.0.6",
"@typescript-eslint/eslint-plugin": "^7.14.1",
"@typescript-eslint/parser": "^7.14.1",
"autoprefixer": "^10.4.19",
Expand Down
2 changes: 1 addition & 1 deletion components/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const LAPIS_URL = 'https://lapis.cov-spectrum.org/open/v2/';
export const LAPIS_URL = 'https://lapis.cov-spectrum.org/open/v2';

export const AGGREGATED_ENDPOINT = `${LAPIS_URL}/sample/aggregated`;
export const NUCLEOTIDE_MUTATIONS_ENDPOINT = `${LAPIS_URL}/sample/nucleotideMutations`;
Expand Down
16 changes: 12 additions & 4 deletions components/src/preact/mutationComparison/queryMutationData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ export function filterMutationData(
data: MutationData[],
displayedSegments: DisplayedSegment[],
displayedMutationTypes: DisplayedMutationType[],
) {
return data.map((mutationEntry) => ({
displayName: mutationEntry.displayName,
data: filterBySegmentAndMutationType(mutationEntry.data, displayedSegments, displayedMutationTypes),
}));
}

export function filterBySegmentAndMutationType(
data: SubstitutionOrDeletionEntry[],
displayedSegments: DisplayedSegment[],
displayedMutationTypes: DisplayedMutationType[],
) {
const byDisplayedSegments = (mutationEntry: SubstitutionOrDeletionEntry) => {
if (mutationEntry.mutation.segment === undefined) {
Expand All @@ -45,8 +56,5 @@ export function filterMutationData(
);
};

return data.map((mutationEntry) => ({
displayName: mutationEntry.displayName,
data: mutationEntry.data.filter(byDisplayedSegments).filter(byDisplayedMutationTypes),
}));
return data.filter(byDisplayedSegments).filter(byDisplayedMutationTypes);
}
92 changes: 92 additions & 0 deletions components/src/preact/mutationOverTime/mutation-over-time-grid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { Fragment, type FunctionComponent } from 'preact';

import {
type MutationOverTimeDataGroupedByMutation,
type MutationOverTimeMutationValue,
} from '../../query/queryMutationOverTime';
import { type Deletion, type Substitution } from '../../utils/mutations';
import { compareTemporal, type Temporal } from '../../utils/temporal';
import { singleGraphColorRGBByName } from '../shared/charts/colors';
import { formatProportion } from '../shared/table/formatProportion';

export interface MutationOverTimeGridProps {
data: MutationOverTimeDataGroupedByMutation;
}

const MutationOverTimeGrid: FunctionComponent<MutationOverTimeGridProps> = ({ data }) => {
const mutations = data.getFirstAxisKeys();
const dates = data.getSecondAxisKeys().sort((a, b) => compareTemporal(a, b));

return (
<div
style={{
display: 'grid',
gridTemplateRows: `repeat(${mutations.length}, 24px)`,
gridTemplateColumns: `8rem repeat(${dates.length}, minmax(1.5rem, 1fr))`,
}}
>
{mutations.map((mutation, i) => {
return (
<Fragment key={`fragment-${mutation.toString()}`}>
<div
key={`mutation-${mutation.toString()}`}
style={{ gridRowStart: i + 1, gridColumnStart: 1 }}
>
<MutationCell mutation={mutation} />
</div>
{dates.map((date, j) => {
const value = data.get(mutation, date) ?? 0;
return (
<div
style={{ gridRowStart: i + 1, gridColumnStart: j + 2 }}
key={`${mutation.toString()}-${date.toString()}`}
>
<ProportionCell value={value} date={date} mutation={mutation} />
</div>
);
})}
</Fragment>
);
})}
</div>
);
};

const ProportionCell: FunctionComponent<{
value: MutationOverTimeMutationValue;
date: Temporal;
mutation: Substitution | Deletion;
}> = ({ value }) => {
// TODO(#353): Add tooltip with date, mutation and proportion
return (
<>
<div className={'py-1'}>
<div
style={{ backgroundColor: backgroundColor(value), color: textColor(value) }}
className='text-center hover:font-bold text-xs'
>
{formatProportion(value, 0)}
</div>
</div>
</>
);
};

const backgroundColor = (proportion: number) => {
// TODO(#353): Make minAlpha and maxAlpha configurable
const minAlpha = 0.0;
const maxAlpha = 1;

const alpha = minAlpha + (maxAlpha - minAlpha) * proportion;
return singleGraphColorRGBByName('indigo', alpha);
};

const textColor = (proportion: number) => {
return proportion > 0.5 ? 'white' : 'black';
};

const MutationCell: FunctionComponent<{ mutation: Substitution | Deletion }> = ({ mutation }) => {
return <div className='text-center'>{mutation.toString()}</div>;
};

export default MutationOverTimeGrid;
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { type FunctionComponent } from 'preact';

import { type MutationOverTimeDataGroupedByMutation } from '../../query/queryMutationOverTime';
import type { Deletion, Substitution } from '../../utils/mutations';
import { Table } from '../components/table';
import { sortSubstitutionsAndDeletions } from '../shared/sort/sortSubstitutionsAndDeletions';
import { formatProportion } from '../shared/table/formatProportion';

export interface MutationOverTimeTableProps {
data: MutationOverTimeDataGroupedByMutation;
pageSize: boolean | number;
}

const MutationOverTimeTable: FunctionComponent<MutationOverTimeTableProps> = ({ data, pageSize }) => {
const getHeaders = () => {
return [
{
name: 'Mutation',
sort: {
compare: sortSubstitutionsAndDeletions,
},
formatter: (cell: Substitution | Deletion) => cell.toString(),
},
...data.getSecondAxisKeys().map((date) => {
return {
name: date.toString(),
sort: true,
formatter: (cell: number) => formatProportion(cell),
};
}),
];
};

const tableData = data.getFirstAxisKeys().map((mutation) => {
return [mutation, ...data.getRow(mutation, 0)];
});

return <Table data={tableData} columns={getHeaders()} pageSize={pageSize} />;
};

export default MutationOverTimeTable;
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { type Meta, type StoryObj } from '@storybook/preact';

import { MutationOverTime, type MutationOverTimeProps } from './mutation-over-time';
import { LAPIS_URL } from '../../constants';
import referenceGenome from '../../lapisApi/__mockData__/referenceGenome.json';
import { LapisUrlContext } from '../LapisUrlContext';
import { ReferenceGenomeContext } from '../ReferenceGenomeContext';

const meta: Meta<MutationOverTimeProps> = {
title: 'Visualization/Mutation over time',
component: MutationOverTime,
argTypes: {
lapisFilter: { control: 'object' },
sequenceType: {
options: ['nucleotide', 'amino acid'],
control: { type: 'radio' },
},
views: {
options: ['table', 'grid', 'insertions'],
control: { type: 'check' },
},
width: { control: 'text' },
height: { control: 'text' },
headline: { control: 'text' },
pageSize: { control: 'object' },
},
};

export default meta;

const Template = {
render: (args: MutationOverTimeProps) => (
<LapisUrlContext.Provider value={LAPIS_URL}>
<ReferenceGenomeContext.Provider value={referenceGenome}>
<MutationOverTime
lapisFilter={args.lapisFilter}
sequenceType={args.sequenceType}
views={args.views}
width={args.width}
height={args.height}
headline={args.headline}
pageSize={args.pageSize}
granularity={args.granularity}
/>
</ReferenceGenomeContext.Provider>
</LapisUrlContext.Provider>
),
};

export const Default: StoryObj<MutationOverTimeProps> = {
...Template,
args: {
lapisFilter: { pangoLineage: 'JN.1*', dateFrom: '2024-01-15', dateTo: '2024-07-10' },
sequenceType: 'nucleotide',
views: ['grid'],
width: '100%',
height: '700px',
headline: 'Mutation over time',
pageSize: true,
granularity: 'year',
},
parameters: {
fetchMock: {
mocks: [],
},
},
};
Loading

0 comments on commit 1bda5d4

Please sign in to comment.