Skip to content

Commit

Permalink
feat(components): number of sequences over time: implement table view
Browse files Browse the repository at this point in the history
closes #318
  • Loading branch information
fengelniederhammer committed Jul 11, 2024
1 parent da0eddd commit 621f06e
Show file tree
Hide file tree
Showing 8 changed files with 219 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { describe, expect, test } from 'vitest';

import { getNumberOfSequencesOverTimeTableData } from './getNumberOfSequencesOverTimeTableData';
import { yearMonth } from '../../utils/temporalTestHelpers';

describe('getNumberOfSequencesOverTimeTableData', () => {
test('should return empty array if no data', () => {
const result = getNumberOfSequencesOverTimeTableData([], 'day');

expect(result).to.deep.equal([]);
});

test('should generate missing date ranges for non-overlapping datasets', () => {
const data = [
{
displayName: 'dataset1',
content: [{ count: 1, dateRange: yearMonth('2023-01') }],
},
{
displayName: 'dataset2',
content: [{ count: 3, dateRange: yearMonth('2023-04') }],
},
];

const result = getNumberOfSequencesOverTimeTableData(data, 'month');

expect(result).to.deep.equal([
{
dataset1: 1,
dataset2: 0,
month: '2023-01',
},
{
dataset1: 0,
dataset2: 0,
month: '2023-02',
},
{
dataset1: 0,
dataset2: 0,
month: '2023-03',
},
{
dataset1: 0,
dataset2: 3,
month: '2023-04',
},
]);
});

test('should maps null date ranges to "unknown"', () => {
const data = [
{
displayName: 'dataset1',
content: [
{ count: 5, dateRange: null },
{ count: 4, dateRange: yearMonth('2023-04') },
],
},
];

const result = getNumberOfSequencesOverTimeTableData(data, 'month');

expect(result).to.deep.equal([
{
dataset1: 5,
month: 'Unknown',
},
{
dataset1: 4,
month: '2023-04',
},
]);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { NumberOfSequencesDatasets } from '../../query/queryNumberOfSequencesOverTime';
import type { TemporalGranularity } from '../../types';
import { generateAllInRange, getMinMaxTemporal, type Temporal } from '../../utils/temporal';

export const getNumberOfSequencesOverTimeTableData = (
data: NumberOfSequencesDatasets,
granularity: TemporalGranularity,
) => {
const datasetsWithCountByDate = data.map(({ displayName, content }) => ({
displayName,
content: new Map(content.map((datum) => [datum.dateRange?.toString(), datum])),
}));

const allDateRangesThatOccurInData = datasetsWithCountByDate
.map(({ content }) => [...content.values()].map((datum) => datum.dateRange))
.reduce((acc, keys) => new Set([...acc, ...keys]), new Set<Temporal | null>());

const minMax = getMinMaxTemporal(allDateRangesThatOccurInData);
if (minMax === null) {
return [];
}

const allDateRanges: (Temporal | null)[] = generateAllInRange(...minMax);

if (allDateRangesThatOccurInData.has(null)) {
allDateRanges.unshift(null);
}

return allDateRanges.map((dateRange) => {
return datasetsWithCountByDate.reduce(
(acc, dataset) => ({
...acc,
[dataset.displayName]: dataset.content.get(dateRange?.toString())?.count ?? 0,
}),
{ [granularity]: dateRange?.toString() ?? 'Unknown' } as Record<string, number | string>,
);
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useMemo } from 'preact/hooks';

import { getNumberOfSequencesOverTimeTableData } from './getNumberOfSequencesOverTimeTableData';
import { type NumberOfSequencesDatasets } from '../../query/queryNumberOfSequencesOverTime';
import { type TemporalGranularity } from '../../types';
import { Table } from '../components/table';

interface NumberSequencesOverTimeTableProps {
data: NumberOfSequencesDatasets;
granularity: TemporalGranularity;
pageSize: boolean | number;
}

export const NumberSequencesOverTimeTable = ({ data, granularity, pageSize }: NumberSequencesOverTimeTableProps) => {
const columns = [
{
name: granularity,
sort: true,
},
...data.map((dataset) => ({
name: dataset.displayName,
sort: true,
})),
];

const flatTableData = useMemo(() => {
const tableData = getNumberOfSequencesOverTimeTableData(data, granularity);
return Object.values(tableData).map((row) => Object.values(row));
}, [data, granularity]);

return <Table data={flatTableData} columns={columns} pageSize={pageSize} />;
};
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export default {
options: ['bar', 'line', 'table'],
control: { type: 'check' },
},
pageSize: { control: 'object' },
},
};

Expand All @@ -34,6 +35,7 @@ const Template: StoryObj<NumberSequencesOverTimeProps> = {
headline={args.headline}
granularity={args.granularity}
smoothingWindow={args.smoothingWindow}
pageSize={args.pageSize}
/>
</LapisUrlContext.Provider>
),
Expand All @@ -48,9 +50,21 @@ const Template: StoryObj<NumberSequencesOverTimeProps> = {
headline: 'Prevalence over time',
smoothingWindow: 0,
granularity: 'month',
pageSize: 10,
},
};

export const Table = {
...Template,
};

export const TwoVariants = {
...Template,
args: {
...Template.args,
lapisFilter: [
{ displayName: 'EG', lapisFilter: { country: 'USA', pangoLineage: 'EG*', dateTo: '2023-06-30' } },
{ displayName: 'JN.1', lapisFilter: { country: 'USA', pangoLineage: 'JN.1*', dateFrom: '2023-01-01' } },
],
},
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { useContext } from 'preact/hooks';

import { queryNumberOfSequencesOverTime } from '../../query/queryNumberOfSequencesOverTime';
import { NumberSequencesOverTimeTable } from './number-sequences-over-time-table';
import {
type NumberOfSequencesDatasets,
queryNumberOfSequencesOverTime,
} from '../../query/queryNumberOfSequencesOverTime';
import type { NamedLapisFilter, TemporalGranularity } from '../../types';
import { LapisUrlContext } from '../LapisUrlContext';
import { ErrorBoundary } from '../components/error-boundary';
Expand All @@ -9,8 +13,11 @@ import Headline from '../components/headline';
import { LoadingDisplay } from '../components/loading-display';
import { NoDataDisplay } from '../components/no-data-display';
import { ResizeContainer } from '../components/resize-container';
import Tabs from '../components/tabs';
import { useQuery } from '../useQuery';

type NumberSequencesOverTimeView = 'bar' | 'line' | 'table';

export interface NumberSequencesOverTimeProps extends NumberSequencesOverTimeInnerProps {
width: string;
height: string;
Expand All @@ -20,12 +27,13 @@ export interface NumberSequencesOverTimeProps extends NumberSequencesOverTimeInn
interface NumberSequencesOverTimeInnerProps {
lapisFilter: NamedLapisFilter | NamedLapisFilter[];
lapisDateField: string;
views: ('bar' | 'line' | 'table')[];
views: NumberSequencesOverTimeView[];
granularity: TemporalGranularity;
smoothingWindow: number;
pageSize: boolean | number;
}

export function NumberSequencesOverTime({ width, height, headline, ...innerProps }: NumberSequencesOverTimeProps) {
export const NumberSequencesOverTime = ({ width, height, headline, ...innerProps }: NumberSequencesOverTimeProps) => {
const size = { height, width };

return (
Expand All @@ -37,13 +45,15 @@ export function NumberSequencesOverTime({ width, height, headline, ...innerProps
</ResizeContainer>
</ErrorBoundary>
);
}
};

const NumberSequencesOverTimeInner = ({
lapisFilter,
granularity,
smoothingWindow,
lapisDateField,
views,
pageSize,
}: NumberSequencesOverTimeInnerProps) => {
const lapis = useContext(LapisUrlContext);

Expand All @@ -63,5 +73,32 @@ const NumberSequencesOverTimeInner = ({
return <NoDataDisplay />;
}

return <div>{JSON.stringify(data)}</div>;
return <NumberSequencesOverTimeTabs views={views} data={data} granularity={granularity} pageSize={pageSize} />;
};

interface NumberSequencesOverTimeTabsProps {
views: NumberSequencesOverTimeView[];
data: NumberOfSequencesDatasets;
granularity: TemporalGranularity;
pageSize: boolean | number;
}

const NumberSequencesOverTimeTabs = ({ views, data, granularity, pageSize }: NumberSequencesOverTimeTabsProps) => {
const getTab = (view: NumberSequencesOverTimeView) => {
switch (view) {
case 'bar':
return { title: 'Bar', content: <div>not implemented, TODO #316</div> };
case 'line':
return { title: 'Line', content: <div>not implemented, TODO #317</div> };
case 'table':
return {
title: 'Table',
content: <NumberSequencesOverTimeTable data={data} granularity={granularity} pageSize={pageSize} />,
};
default:
throw new Error(`Unknown view: ${view}`);
}
};

return <Tabs tabs={views.map((view) => getTab(view))} />;
};
10 changes: 1 addition & 9 deletions components/src/query/queryNumberOfSequencesOverTime.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { describe, expect, test } from 'vitest';

import { queryNumberOfSequencesOverTime } from './queryNumberOfSequencesOverTime';
import { DUMMY_LAPIS_URL, lapisRequestMocks } from '../../vitest.setup';
import { TemporalCache, YearMonth, YearMonthDay } from '../utils/temporal';
import { yearMonth, yearMonthDay } from '../utils/temporalTestHelpers';

const lapisDateField = 'dateField';
const lapisFilter = { field1: 'value1', field2: 'value2' };
Expand Down Expand Up @@ -193,11 +193,3 @@ describe('queryNumberOfSequencesOverTime', () => {
]);
});
});

function yearMonthDay(date: string) {
return YearMonthDay.parse(date, TemporalCache.getInstance());
}

function yearMonth(date: string) {
return YearMonth.parse(date, TemporalCache.getInstance());
}
9 changes: 9 additions & 0 deletions components/src/utils/temporalTestHelpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { TemporalCache, YearMonth, YearMonthDay } from './temporal';

export function yearMonthDay(date: string) {
return YearMonthDay.parse(date, TemporalCache.getInstance());
}

export function yearMonth(date: string) {
return YearMonth.parse(date, TemporalCache.getInstance());
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,13 @@ export class NumberSequencesOverTimeComponent extends PreactLitAdapterWithGridJs
@property({ type: Number })
smoothingWindow: number = 0;

/**
* The maximum number of rows to display in the table view.
* Set to `false` to disable pagination. Set to `true` to enable pagination with a default limit (10).
*/
@property({ type: Object })
pageSize: boolean | number = false;

override render() {
return (
<NumberSequencesOverTime
Expand All @@ -99,6 +106,7 @@ export class NumberSequencesOverTimeComponent extends PreactLitAdapterWithGridJs
height={this.height}
granularity={this.granularity}
smoothingWindow={this.smoothingWindow}
pageSize={this.pageSize}
/>
);
}
Expand Down

0 comments on commit 621f06e

Please sign in to comment.