@genspectrum/dashboard-components 1.15.0 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/custom-elements.json +5 -5
  2. package/dist/components.d.ts +66 -67
  3. package/dist/components.js +457 -256
  4. package/dist/components.js.map +1 -1
  5. package/dist/util.d.ts +69 -66
  6. package/package.json +2 -9
  7. package/src/preact/MutationAnnotationsContext.tsx +1 -1
  8. package/src/preact/components/csv-download-button.tsx +22 -14
  9. package/src/preact/components/features-over-time-grid.tsx +189 -43
  10. package/src/preact/components/mutations-over-time-mutations-filter.stories.tsx +1 -1
  11. package/src/preact/components/mutations-over-time-mutations-filter.tsx +1 -1
  12. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutationsByDay/aminoAcidMutationsOverTimePage1.json +52 -0
  13. package/src/preact/mutationsOverTime/__mockData__/byWeek/mutationsOverTimePage1.json +76 -0
  14. package/src/preact/mutationsOverTime/__mockData__/defaultMockData/mockDefaultMutationsOverTimeWithFilter.json +43 -0
  15. package/src/preact/mutationsOverTime/__mockData__/defaultMockData/mutationsOverTimePage1.json +126 -0
  16. package/src/preact/mutationsOverTime/__mockData__/defaultMockData/mutationsOverTimePage2.json +116 -0
  17. package/src/preact/mutationsOverTime/__mockData__/defaultMockData/mutationsOverTimePageSize20.json +216 -0
  18. package/src/preact/mutationsOverTime/getFilteredMutationCodes.spec.ts +236 -0
  19. package/src/preact/mutationsOverTime/{getFilteredMutationsOverTimeData.ts → getFilteredMutationCodes.ts} +32 -45
  20. package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +128 -23
  21. package/src/preact/mutationsOverTime/mutations-over-time.tsx +139 -74
  22. package/src/preact/mutationsOverTime/useMutationsOverTimePageData.ts +111 -0
  23. package/src/preact/queriesOverTime/queries-over-time-row-label-tooltip.stories.tsx +8 -10
  24. package/src/preact/queriesOverTime/queries-over-time-row-label-tooltip.tsx +7 -17
  25. package/src/preact/queriesOverTime/queries-over-time.tsx +1 -1
  26. package/src/preact/shared/tanstackTable/pagination-context.tsx +5 -2
  27. package/src/preact/shared/tanstackTable/pagination.tsx +11 -9
  28. package/src/preact/shared/tanstackTable/tanstackTable.tsx +7 -4
  29. package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.tsx +1 -1
  30. package/src/query/queryMutationsOverTime.spec.ts +187 -662
  31. package/src/query/queryMutationsOverTime.ts +46 -33
  32. package/src/utilEntrypoint.ts +1 -1
  33. package/src/utils/useControlledState.ts +15 -0
  34. package/src/web-components/gs-app.ts +2 -3
  35. package/src/web-components/input/gs-date-range-filter.tsx +2 -3
  36. package/src/web-components/input/gs-lineage-filter.tsx +2 -3
  37. package/src/web-components/input/gs-location-filter.tsx +2 -3
  38. package/src/web-components/input/gs-mutation-filter.tsx +3 -4
  39. package/src/web-components/input/gs-number-range-filter.tsx +2 -3
  40. package/src/web-components/input/gs-text-filter.tsx +2 -3
  41. package/src/web-components/mutation-annotations-context.ts +3 -1
  42. package/src/web-components/visualization/gs-aggregate.tsx +2 -3
  43. package/src/web-components/visualization/gs-genome-data-viewer.tsx +2 -3
  44. package/src/web-components/visualization/gs-mutation-comparison.tsx +2 -3
  45. package/src/web-components/visualization/gs-mutations-over-time.stories.ts +78 -22
  46. package/src/web-components/visualization/gs-mutations-over-time.tsx +2 -3
  47. package/src/web-components/visualization/gs-mutations.tsx +7 -8
  48. package/src/web-components/visualization/gs-number-sequences-over-time.tsx +2 -3
  49. package/src/web-components/visualization/gs-prevalence-over-time.tsx +2 -3
  50. package/src/web-components/visualization/gs-queries-over-time.tsx +2 -3
  51. package/src/web-components/visualization/gs-relative-growth-advantage.tsx +2 -3
  52. package/src/web-components/visualization/gs-sequences-by-location.tsx +2 -3
  53. package/src/web-components/visualization/gs-statistics.tsx +2 -3
  54. package/src/web-components/wastewaterVisualization/gs-wastewater-mutations-over-time.tsx +2 -3
  55. package/standalone-bundle/dashboard-components.js +6877 -6697
  56. package/standalone-bundle/dashboard-components.js.map +1 -1
  57. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutationsByDay/aminoAcidMutationsOverTime.json +0 -5496
  58. package/src/preact/mutationsOverTime/__mockData__/byWeek/mutationsOverTime.json +0 -7100
  59. package/src/preact/mutationsOverTime/__mockData__/defaultMockData/mutationsOverTime.json +0 -12646
  60. package/src/preact/mutationsOverTime/getFilteredMutationsOverTime.spec.ts +0 -417
@@ -1,15 +1,14 @@
1
1
  import z from 'zod';
2
2
 
3
- import { type MutationOverTimeDataMap } from './MutationOverTimeData';
4
3
  import { type SequenceType, type SubstitutionOrDeletionEntry } from '../../types';
5
- import { Map2dView } from '../../utils/map2d';
6
4
  import type { Deletion, Mutation, Substitution } from '../../utils/mutations';
7
5
  import { type useMutationAnnotationsProvider } from '../MutationAnnotationsContext';
8
6
  import type { DisplayedMutationType } from '../components/mutation-type-selector';
9
7
  import type { DisplayedSegment } from '../components/segment-selector';
10
8
 
11
- export const displayMutationsSchema = z.array(z.string());
12
- export type DisplayMutations = z.infer<typeof displayMutationsSchema>;
9
+ export const displayMutationsSchema = z.array(z.string(), {
10
+ errorMap: () => ({ message: `invalid display mutations` }),
11
+ });
13
12
 
14
13
  export type MutationFilter = {
15
14
  textFilter: string;
@@ -17,63 +16,51 @@ export type MutationFilter = {
17
16
  };
18
17
 
19
18
  export type GetFilteredMutationOverTimeDataArgs = {
20
- data: MutationOverTimeDataMap;
21
19
  overallMutationData: SubstitutionOrDeletionEntry<Substitution, Deletion>[];
22
20
  displayedSegments: DisplayedSegment[];
23
21
  displayedMutationTypes: DisplayedMutationType[];
24
22
  proportionInterval: { min: number; max: number };
25
- hideGaps: boolean;
26
- displayMutations?: DisplayMutations;
27
23
  mutationFilterValue: MutationFilter;
28
24
  sequenceType: SequenceType;
29
25
  annotationProvider: ReturnType<typeof useMutationAnnotationsProvider>;
30
26
  };
31
27
 
32
- export function getFilteredMutationOverTimeData({
33
- data,
28
+ /**
29
+ * Extracts a list of mutation codes that should be displayed based on the provided filters and overall mutation data.
30
+ */
31
+ export function getFilteredMutationCodes({
34
32
  overallMutationData,
35
33
  displayedSegments,
36
34
  displayedMutationTypes,
37
35
  proportionInterval,
38
- hideGaps,
39
36
  mutationFilterValue,
40
37
  sequenceType,
41
38
  annotationProvider,
42
- }: GetFilteredMutationOverTimeDataArgs) {
43
- const filteredData = new Map2dView(data);
44
-
45
- const mutationsToFilterOut = overallMutationData.filter((entry) => {
46
- if (entry.proportion < proportionInterval.min || entry.proportion > proportionInterval.max) {
47
- return true;
48
- }
49
- if (displayedSegments.some((segment) => segment.segment === entry.mutation.segment && !segment.checked)) {
50
- return true;
51
- }
52
-
53
- if (
54
- mutationOrAnnotationDoNotMatchFilter(entry.mutation, sequenceType, mutationFilterValue, annotationProvider)
55
- ) {
56
- return true;
57
- }
58
-
59
- return displayedMutationTypes.some(
60
- (mutationType) => mutationType.type === entry.mutation.type && !mutationType.checked,
61
- );
62
- });
63
-
64
- mutationsToFilterOut.forEach((entry) => {
65
- filteredData.deleteRow(entry.mutation);
66
- });
67
-
68
- if (hideGaps) {
69
- const dateRangesToFilterOut = filteredData.getSecondAxisKeys().filter((dateRange) => {
70
- const vals = filteredData.getColumn(dateRange);
71
- return !vals.some((v) => (v?.type === 'value' || v?.type === 'valueWithCoverage') && v.totalCount > 0);
72
- });
73
- dateRangesToFilterOut.forEach((dateRange) => filteredData.deleteColumn(dateRange));
74
- }
75
-
76
- return filteredData;
39
+ }: GetFilteredMutationOverTimeDataArgs): string[] {
40
+ return overallMutationData
41
+ .filter((entry) => {
42
+ if (entry.proportion < proportionInterval.min || entry.proportion > proportionInterval.max) {
43
+ return false;
44
+ }
45
+ if (displayedSegments.some((segment) => segment.segment === entry.mutation.segment && !segment.checked)) {
46
+ return false;
47
+ }
48
+
49
+ if (
50
+ mutationOrAnnotationDoNotMatchFilter(
51
+ entry.mutation,
52
+ sequenceType,
53
+ mutationFilterValue,
54
+ annotationProvider,
55
+ )
56
+ ) {
57
+ return false;
58
+ }
59
+ return !displayedMutationTypes.some(
60
+ (mutationType) => mutationType.type === entry.mutation.type && !mutationType.checked,
61
+ );
62
+ })
63
+ .map((e) => e.mutation.code);
77
64
  }
78
65
 
79
66
  export function mutationOrAnnotationDoNotMatchFilter(
@@ -8,7 +8,10 @@ import { type MutationAnnotations } from '../../web-components/mutation-annotati
8
8
  import { LapisUrlContextProvider } from '../LapisUrlContext';
9
9
  import { MutationAnnotationsContextProvider } from '../MutationAnnotationsContext';
10
10
  import { ReferenceGenomeContext } from '../ReferenceGenomeContext';
11
- import mockDefaultMutationsOverTime from './__mockData__/defaultMockData/mutationsOverTime.json';
11
+ import mockDefaultMutationsOverTimeWithFilter from './__mockData__/defaultMockData/mockDefaultMutationsOverTimeWithFilter.json';
12
+ import mockDefaultMutationsOverTimePage1 from './__mockData__/defaultMockData/mutationsOverTimePage1.json';
13
+ import mockDefaultMutationsOverTimePage2 from './__mockData__/defaultMockData/mutationsOverTimePage2.json';
14
+ import mockDefaultMutationsOverTimePageSize20 from './__mockData__/defaultMockData/mutationsOverTimePageSize20.json';
12
15
  import mockDefaultNucleotideMutations from './__mockData__/defaultMockData/nucleotideMutations.json';
13
16
  import mock1800sMutationsOverTime from './__mockData__/request1800s/mutationsOverTime.json';
14
17
  import mock1800sNucleotideMutations from './__mockData__/request1800s/nucleotideMutations.json';
@@ -64,13 +67,10 @@ const meta: Meta<MutationsOverTimeProps> = {
64
67
  },
65
68
  {
66
69
  matcher: {
70
+ name: 'page1',
67
71
  url: `${LAPIS_URL}/component/nucleotideMutationsOverTime`,
68
72
  body: {
69
- filters: {
70
- pangoLineage: 'JN.1*',
71
- dateFrom: '2024-01-15',
72
- dateTo: '2024-07-10',
73
- },
73
+ filters: { pangoLineage: 'JN.1*', dateFrom: '2024-01-15', dateTo: '2024-07-10' },
74
74
  dateRanges: [
75
75
  { dateFrom: '2024-01-01', dateTo: '2024-01-31' },
76
76
  { dateFrom: '2024-02-01', dateTo: '2024-02-29' },
@@ -80,12 +80,125 @@ const meta: Meta<MutationsOverTimeProps> = {
80
80
  { dateFrom: '2024-06-01', dateTo: '2024-06-30' },
81
81
  { dateFrom: '2024-07-01', dateTo: '2024-07-31' },
82
82
  ],
83
+ includeMutations: [
84
+ 'C44T',
85
+ 'C774T',
86
+ 'C7113T',
87
+ 'C12616T',
88
+ 'A13121T',
89
+ 'G15372T',
90
+ 'G17334T',
91
+ 'T18453C',
92
+ 'A19722G',
93
+ 'T21653-',
94
+ ],
83
95
  dateField: 'date',
84
96
  },
85
- matchPartialBody: true, // includeMutations left out
86
97
  response: {
87
98
  status: 200,
88
- body: mockDefaultMutationsOverTime,
99
+ body: mockDefaultMutationsOverTimePage1,
100
+ },
101
+ },
102
+ },
103
+ {
104
+ matcher: {
105
+ name: 'page2',
106
+ url: `${LAPIS_URL}/component/nucleotideMutationsOverTime`,
107
+ body: {
108
+ filters: { pangoLineage: 'JN.1*', dateFrom: '2024-01-15', dateTo: '2024-07-10' },
109
+ dateRanges: [
110
+ { dateFrom: '2024-01-01', dateTo: '2024-01-31' },
111
+ { dateFrom: '2024-02-01', dateTo: '2024-02-29' },
112
+ { dateFrom: '2024-03-01', dateTo: '2024-03-31' },
113
+ { dateFrom: '2024-04-01', dateTo: '2024-04-30' },
114
+ { dateFrom: '2024-05-01', dateTo: '2024-05-31' },
115
+ { dateFrom: '2024-06-01', dateTo: '2024-06-30' },
116
+ { dateFrom: '2024-07-01', dateTo: '2024-07-31' },
117
+ ],
118
+ includeMutations: [
119
+ 'C21654-',
120
+ 'T21655-',
121
+ 'G22111T',
122
+ 'G22599C',
123
+ 'T22928C',
124
+ 'T23011-',
125
+ 'C23039G',
126
+ 'C23277T',
127
+ 'G24872T',
128
+ ],
129
+ dateField: 'date',
130
+ },
131
+ response: {
132
+ status: 200,
133
+ body: mockDefaultMutationsOverTimePage2,
134
+ },
135
+ },
136
+ },
137
+ {
138
+ matcher: {
139
+ name: 'page1-pageSize20',
140
+ url: `${LAPIS_URL}/component/nucleotideMutationsOverTime`,
141
+ body: {
142
+ filters: { pangoLineage: 'JN.1*', dateFrom: '2024-01-15', dateTo: '2024-07-10' },
143
+ dateRanges: [
144
+ { dateFrom: '2024-01-01', dateTo: '2024-01-31' },
145
+ { dateFrom: '2024-02-01', dateTo: '2024-02-29' },
146
+ { dateFrom: '2024-03-01', dateTo: '2024-03-31' },
147
+ { dateFrom: '2024-04-01', dateTo: '2024-04-30' },
148
+ { dateFrom: '2024-05-01', dateTo: '2024-05-31' },
149
+ { dateFrom: '2024-06-01', dateTo: '2024-06-30' },
150
+ { dateFrom: '2024-07-01', dateTo: '2024-07-31' },
151
+ ],
152
+ includeMutations: [
153
+ 'C44T',
154
+ 'C774T',
155
+ 'C7113T',
156
+ 'C12616T',
157
+ 'A13121T',
158
+ 'G15372T',
159
+ 'G17334T',
160
+ 'T18453C',
161
+ 'A19722G',
162
+ 'T21653-',
163
+ 'C21654-',
164
+ 'T21655-',
165
+ 'G22111T',
166
+ 'G22599C',
167
+ 'T22928C',
168
+ 'T23011-',
169
+ 'C23039G',
170
+ 'C23277T',
171
+ 'G24872T',
172
+ ],
173
+ dateField: 'date',
174
+ },
175
+ response: {
176
+ status: 200,
177
+ body: mockDefaultMutationsOverTimePageSize20,
178
+ },
179
+ },
180
+ },
181
+ {
182
+ matcher: {
183
+ name: 'withFilter',
184
+ url: `${LAPIS_URL}/component/nucleotideMutationsOverTime`,
185
+ body: {
186
+ filters: { pangoLineage: 'JN.1*', dateFrom: '2024-01-15', dateTo: '2024-07-10' },
187
+ dateRanges: [
188
+ { dateFrom: '2024-01-01', dateTo: '2024-01-31' },
189
+ { dateFrom: '2024-02-01', dateTo: '2024-02-29' },
190
+ { dateFrom: '2024-03-01', dateTo: '2024-03-31' },
191
+ { dateFrom: '2024-04-01', dateTo: '2024-04-30' },
192
+ { dateFrom: '2024-05-01', dateTo: '2024-05-31' },
193
+ { dateFrom: '2024-06-01', dateTo: '2024-06-30' },
194
+ { dateFrom: '2024-07-01', dateTo: '2024-07-31' },
195
+ ],
196
+ includeMutations: ['T21653-', 'T21655-'],
197
+ dateField: 'date',
198
+ },
199
+ response: {
200
+ status: 200,
201
+ body: mockDefaultMutationsOverTimeWithFilter,
89
202
  },
90
203
  },
91
204
  },
@@ -270,7 +383,7 @@ export const UsesPagination: StoryObj<MutationsOverTimeProps> = {
270
383
  ...Default,
271
384
  play: async ({ canvas, step }) => {
272
385
  const mutationOnFirstPage = 'C44T';
273
- const mutationOnSecondPage = 'T21653-';
386
+ const mutationOnSecondPage = 'C21654-';
274
387
  await expectMutationOnPage(canvas, mutationOnFirstPage);
275
388
 
276
389
  await step('Navigate to next page', async () => {
@@ -279,11 +392,8 @@ export const UsesPagination: StoryObj<MutationsOverTimeProps> = {
279
392
  await expectMutationOnPage(canvas, mutationOnSecondPage);
280
393
  });
281
394
 
282
- await step('Use goto page input', async () => {
283
- const gotoPageInput = canvas.getByRole('spinbutton', { name: 'Enter page number to go to' });
284
- await userEvent.clear(gotoPageInput);
285
- await userEvent.type(gotoPageInput, '1');
286
- await userEvent.tab();
395
+ await step('Go to previous page', async () => {
396
+ canvas.getByRole('button', { name: 'Previous page' }).click();
287
397
 
288
398
  await expectMutationOnPage(canvas, mutationOnFirstPage);
289
399
  });
@@ -448,16 +558,11 @@ export const ShowsNoDataMessageForStrictFilters: StoryObj<MutationsOverTimeProps
448
558
  play: async ({ canvas }) => {
449
559
  await waitFor(() => expect(canvas.getByText('Grid')).toBeVisible(), { timeout: 10000 });
450
560
 
451
- const button = canvas.getByRole('button', { name: 'Mean proportion 5.0% - 90.0%' });
452
- await userEvent.click(button);
453
-
454
- const minInput = canvas.getAllByLabelText('%')[0];
455
- await userEvent.clear(minInput);
456
- await userEvent.type(minInput, '40');
561
+ const filterButton = canvas.getByRole('button', { name: 'Filter mutations' });
562
+ await userEvent.click(filterButton);
457
563
 
458
- const maxInput = canvas.getAllByLabelText('%')[1];
459
- await userEvent.clear(maxInput);
460
- await userEvent.type(maxInput, '41');
564
+ const filterInput = canvas.getByPlaceholderText('Filter');
565
+ await userEvent.type(filterInput, 'too strict');
461
566
 
462
567
  await waitFor(
463
568
  () => expect(canvas.getByText('No data available for your filters.', { exact: false })).toBeVisible(),
@@ -1,33 +1,33 @@
1
1
  import { type FunctionComponent } from 'preact';
2
- import { type Dispatch, type StateUpdater, useMemo, useState, useEffect, useLayoutEffect, useRef } from 'preact/hooks';
2
+ import { type Dispatch, type StateUpdater, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'preact/hooks';
3
3
  import z from 'zod';
4
4
 
5
- import { type BaseMutationOverTimeDataMap, type MutationOverTimeDataMap } from './MutationOverTimeData';
6
- import {
7
- displayMutationsSchema,
8
- getFilteredMutationOverTimeData,
9
- type MutationFilter,
10
- } from './getFilteredMutationsOverTimeData';
5
+ import { displayMutationsSchema, getFilteredMutationCodes, type MutationFilter } from './getFilteredMutationCodes';
11
6
  import { MutationsOverTimeGridTooltip } from './mutations-over-time-grid-tooltip';
12
- import { type ProportionValue, getProportion, queryMutationsOverTimeData } from '../../query/queryMutationsOverTime';
13
7
  import {
14
- lapisFilterSchema,
15
- sequenceTypeSchema,
16
- type SubstitutionOrDeletionEntry,
17
- temporalGranularitySchema,
18
- views,
19
- } from '../../types';
8
+ getProportion,
9
+ type MutationsOverTimeMetadata,
10
+ type ProportionValue,
11
+ queryMutationsOverTimeMetadata,
12
+ queryMutationsOverTimePage,
13
+ } from '../../query/queryMutationsOverTime';
14
+ import { lapisFilterSchema, sequenceTypeSchema, temporalGranularitySchema, views } from '../../types';
20
15
  import { type Deletion, type Substitution } from '../../utils/mutations';
21
16
  import { type Temporal, toTemporalClass } from '../../utils/temporalClass';
22
17
  import { useDispatchFinishedLoadingEvent } from '../../utils/useDispatchFinishedLoadingEvent';
23
18
  import { useLapisUrl } from '../LapisUrlContext';
24
19
  import { useMutationAnnotationsProvider } from '../MutationAnnotationsContext';
20
+ import { type MutationOverTimeDataMap } from './MutationOverTimeData';
25
21
  import { AnnotatedMutation } from '../components/annotated-mutation';
26
22
  import { type ColorScale } from '../components/color-scale-selector';
27
23
  import { ColorScaleSelectorDropdown } from '../components/color-scale-selector-dropdown';
28
24
  import { CsvDownloadButton } from '../components/csv-download-button';
29
25
  import { ErrorBoundary } from '../components/error-boundary';
30
- import FeaturesOverTimeGrid, { type FeatureRenderer, customColumnSchema } from '../components/features-over-time-grid';
26
+ import {
27
+ customColumnSchema,
28
+ type FeatureRenderer,
29
+ FeaturesOverTimeGridServerPaginated,
30
+ } from '../components/features-over-time-grid';
31
31
  import { Fullscreen } from '../components/fullscreen';
32
32
  import Info, { InfoComponentCode, InfoHeadline1, InfoParagraph } from '../components/info';
33
33
  import { LoadingDisplay } from '../components/loading-display';
@@ -40,8 +40,9 @@ import { ResizeContainer } from '../components/resize-container';
40
40
  import { type DisplayedSegment, SegmentSelector, useDisplayedSegments } from '../components/segment-selector';
41
41
  import Tabs from '../components/tabs';
42
42
  import { pageSizesSchema } from '../shared/tanstackTable/pagination';
43
- import { PageSizeContextProvider } from '../shared/tanstackTable/pagination-context';
43
+ import { PageSizeContextProvider, usePageSizeContext } from '../shared/tanstackTable/pagination-context';
44
44
  import { useQuery } from '../useQuery';
45
+ import { handleHideGaps, useMutationsOverTimePageData } from './useMutationsOverTimePageData';
45
46
 
46
47
  const mutationsOverTimeViewSchema = z.literal(views.grid);
47
48
  export type MutationsOverTimeView = z.infer<typeof mutationsOverTimeViewSchema>;
@@ -83,47 +84,68 @@ export const MutationsOverTime: FunctionComponent<MutationsOverTimeProps> = (com
83
84
 
84
85
  export const MutationsOverTimeInner: FunctionComponent<MutationsOverTimeProps> = ({ ...componentProps }) => {
85
86
  const lapis = useLapisUrl();
86
- const { lapisFilter, sequenceType, granularity, lapisDateField, displayMutations } = componentProps;
87
-
88
- const { data, error, isLoading } = useQuery(
89
- () =>
90
- queryMutationsOverTimeData(lapisFilter, sequenceType, lapis, lapisDateField, granularity, displayMutations),
91
- [granularity, lapis, lapisDateField, lapisFilter, sequenceType, displayMutations],
92
- );
87
+ const { lapisFilter, sequenceType, granularity, lapisDateField, displayMutations, pageSizes } = componentProps;
88
+
89
+ const [pageIndex, setPageIndex] = useState(0);
90
+
91
+ const {
92
+ data: metadata,
93
+ error: metadataError,
94
+ isLoading: metadataLoading,
95
+ } = useQuery(() => {
96
+ setPageIndex(0);
97
+ return queryMutationsOverTimeMetadata(
98
+ lapisFilter,
99
+ sequenceType,
100
+ lapis,
101
+ lapisDateField,
102
+ granularity,
103
+ displayMutations,
104
+ );
105
+ }, [granularity, lapis, lapisDateField, lapisFilter, sequenceType, displayMutations]);
93
106
 
94
- if (isLoading) {
107
+ if (metadataLoading) {
95
108
  return <LoadingDisplay />;
96
109
  }
97
110
 
98
- if (error !== null) {
99
- throw error;
111
+ if (metadataError) {
112
+ throw metadataError;
100
113
  }
101
114
 
102
- if (data.overallMutationData.length === 0) {
115
+ if (metadata.overallMutationData.length === 0) {
103
116
  return <NoDataDisplay />;
104
117
  }
105
118
 
106
- const { overallMutationData, mutationOverTimeData } = data;
107
119
  return (
108
- <MutationsOverTimeTabs
109
- overallMutationData={overallMutationData}
110
- mutationOverTimeData={mutationOverTimeData}
111
- originalComponentProps={componentProps}
112
- />
120
+ <PageSizeContextProvider pageSizes={pageSizes}>
121
+ <MutationsOverTimeTabs
122
+ metadata={metadata}
123
+ originalComponentProps={componentProps}
124
+ pageIndex={pageIndex}
125
+ setPageIndex={setPageIndex}
126
+ />
127
+ </PageSizeContextProvider>
113
128
  );
114
129
  };
115
130
 
116
131
  type MutationOverTimeTabsProps = {
117
- mutationOverTimeData: BaseMutationOverTimeDataMap;
132
+ metadata: MutationsOverTimeMetadata;
118
133
  originalComponentProps: MutationsOverTimeProps;
119
- overallMutationData: SubstitutionOrDeletionEntry<Substitution, Deletion>[];
134
+ pageIndex: number;
135
+ setPageIndex: Dispatch<StateUpdater<number>>;
120
136
  };
121
137
 
122
138
  const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
123
- mutationOverTimeData,
139
+ metadata,
124
140
  originalComponentProps,
125
- overallMutationData,
141
+ pageIndex,
142
+ setPageIndex,
126
143
  }) => {
144
+ const lapis = useLapisUrl();
145
+ const { lapisFilter, sequenceType, lapisDateField } = originalComponentProps;
146
+ const { overallMutationData, requestedDateRanges } = metadata;
147
+ const { pageSize } = usePageSizeContext();
148
+
127
149
  const tabsRef = useDispatchFinishedLoadingEvent();
128
150
  const tooltipPortalTargetRef = useRef<HTMLDivElement>(null);
129
151
  const [tooltipPortalTarget, setTooltipPortalTarget] = useState<HTMLDivElement | null>(null);
@@ -148,44 +170,65 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
148
170
  ]);
149
171
 
150
172
  const [hideGaps, setHideGaps] = useState<boolean>(originalComponentProps.hideGaps ?? false);
151
-
152
173
  useEffect(() => setHideGaps(originalComponentProps.hideGaps ?? false), [originalComponentProps.hideGaps]);
153
174
 
154
- const filteredData = useMemo(() => {
155
- return getFilteredMutationOverTimeData({
156
- data: mutationOverTimeData,
175
+ const filteredMutationCodes = useMemo(
176
+ () =>
177
+ getFilteredMutationCodes({
178
+ overallMutationData,
179
+ displayedSegments,
180
+ displayedMutationTypes,
181
+ proportionInterval,
182
+ mutationFilterValue,
183
+ sequenceType: originalComponentProps.sequenceType,
184
+ annotationProvider,
185
+ }),
186
+ [
157
187
  overallMutationData,
158
188
  displayedSegments,
159
189
  displayedMutationTypes,
160
190
  proportionInterval,
161
- hideGaps,
191
+ originalComponentProps.sequenceType,
162
192
  mutationFilterValue,
163
- sequenceType: originalComponentProps.sequenceType,
164
193
  annotationProvider,
165
- });
166
- }, [
167
- mutationOverTimeData,
168
- overallMutationData,
169
- displayedSegments,
170
- displayedMutationTypes,
171
- proportionInterval,
194
+ ],
195
+ );
196
+
197
+ useEffect(() => {
198
+ setPageIndex(0);
199
+ }, [filteredMutationCodes, setPageIndex]);
200
+
201
+ const totalFilteredRows = filteredMutationCodes.length;
202
+ const {
203
+ isLoading: isPageLoading,
204
+ data: pageData,
205
+ pageMutationCodes,
206
+ } = useMutationsOverTimePageData(
207
+ filteredMutationCodes,
208
+ pageIndex,
209
+ pageSize,
210
+ lapisFilter,
211
+ lapis,
212
+ lapisDateField,
213
+ sequenceType,
214
+ requestedDateRanges,
172
215
  hideGaps,
173
- originalComponentProps.sequenceType,
174
- mutationFilterValue,
175
- annotationProvider,
176
- ]);
216
+ );
177
217
 
178
- const mutationRenderer: FeatureRenderer<Substitution | Deletion> = {
179
- asString: (value: Substitution | Deletion) => value.code,
180
- renderRowLabel: (value: Substitution | Deletion) => (
181
- <div className={'text-center'}>
182
- <AnnotatedMutation mutation={value} sequenceType={originalComponentProps.sequenceType} />
183
- </div>
184
- ),
185
- renderTooltip: (value: Substitution | Deletion, temporal: Temporal, proportionValue: ProportionValue) => (
186
- <MutationsOverTimeGridTooltip mutation={value} date={temporal} value={proportionValue} />
187
- ),
188
- };
218
+ const mutationRenderer: FeatureRenderer<Substitution | Deletion> = useMemo(
219
+ () => ({
220
+ asString: (value: Substitution | Deletion) => value.code,
221
+ renderRowLabel: (value: Substitution | Deletion) => (
222
+ <div className={'text-center'}>
223
+ <AnnotatedMutation mutation={value} sequenceType={originalComponentProps.sequenceType} />
224
+ </div>
225
+ ),
226
+ renderTooltip: (value: Substitution | Deletion, temporal: Temporal, proportionValue: ProportionValue) => (
227
+ <MutationsOverTimeGridTooltip mutation={value} date={temporal} value={proportionValue} />
228
+ ),
229
+ }),
230
+ [originalComponentProps.sequenceType],
231
+ );
189
232
 
190
233
  const getTab = (view: MutationsOverTimeView) => {
191
234
  switch (view) {
@@ -194,11 +237,17 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
194
237
  return {
195
238
  title: 'Grid',
196
239
  content: (
197
- <FeaturesOverTimeGrid
240
+ <FeaturesOverTimeGridServerPaginated
198
241
  rowLabelHeader='Mutation'
199
- data={filteredData}
242
+ data={pageData}
243
+ isLoading={isPageLoading}
244
+ loadingRowLabels={pageMutationCodes}
245
+ requestedDateRanges={requestedDateRanges}
200
246
  colorScale={colorScale}
201
247
  pageSizes={originalComponentProps.pageSizes}
248
+ pageIndex={pageIndex}
249
+ totalRows={totalFilteredRows}
250
+ onPageChange={setPageIndex}
202
251
  customColumns={originalComponentProps.customColumns}
203
252
  featureRenderer={mutationRenderer}
204
253
  tooltipPortalTarget={tooltipPortalTarget}
@@ -221,20 +270,19 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
221
270
  setProportionInterval={setProportionInterval}
222
271
  hideGaps={hideGaps}
223
272
  setHideGaps={setHideGaps}
224
- filteredData={filteredData}
225
273
  colorScale={colorScale}
226
274
  setColorScale={setColorScale}
227
275
  originalComponentProps={originalComponentProps}
228
276
  setFilterValue={setMutationFilterValue}
229
277
  mutationFilterValue={mutationFilterValue}
278
+ filteredMutationCodes={filteredMutationCodes}
279
+ metadata={metadata}
230
280
  />
231
281
  );
232
282
 
233
283
  return (
234
284
  <div ref={tooltipPortalTargetRef}>
235
- <PageSizeContextProvider pageSizes={originalComponentProps.pageSizes}>
236
- <Tabs ref={tabsRef} tabs={tabs} toolbar={toolbar} />
237
- </PageSizeContextProvider>
285
+ <Tabs ref={tabsRef} tabs={tabs} toolbar={toolbar} />
238
286
  </div>
239
287
  );
240
288
  };
@@ -249,12 +297,13 @@ type ToolbarProps = {
249
297
  setProportionInterval: Dispatch<StateUpdater<ProportionInterval>>;
250
298
  hideGaps: boolean;
251
299
  setHideGaps: Dispatch<StateUpdater<boolean>>;
252
- filteredData: MutationOverTimeDataMap;
253
300
  colorScale: ColorScale;
254
301
  setColorScale: Dispatch<StateUpdater<ColorScale>>;
255
302
  originalComponentProps: MutationsOverTimeProps;
256
303
  mutationFilterValue: MutationFilter;
257
304
  setFilterValue: Dispatch<StateUpdater<MutationFilter>>;
305
+ filteredMutationCodes: string[];
306
+ metadata: MutationsOverTimeMetadata;
258
307
  };
259
308
 
260
309
  const Toolbar: FunctionComponent<ToolbarProps> = ({
@@ -267,13 +316,29 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
267
316
  setProportionInterval,
268
317
  hideGaps,
269
318
  setHideGaps,
270
- filteredData,
271
319
  colorScale,
272
320
  setColorScale,
273
321
  originalComponentProps,
274
322
  setFilterValue,
275
323
  mutationFilterValue,
324
+ filteredMutationCodes,
325
+ metadata,
276
326
  }) => {
327
+ const lapis = useLapisUrl();
328
+ const { lapisFilter, sequenceType, lapisDateField } = originalComponentProps;
329
+
330
+ const getDownloadDataAsync = async (): Promise<Record<string, string | number>[]> => {
331
+ const pageData = await queryMutationsOverTimePage(
332
+ lapisFilter,
333
+ lapis,
334
+ lapisDateField,
335
+ sequenceType,
336
+ metadata.requestedDateRanges,
337
+ filteredMutationCodes,
338
+ );
339
+ return getDownloadData(handleHideGaps(pageData, hideGaps));
340
+ };
341
+
277
342
  return (
278
343
  <>
279
344
  <MutationsOverTimeMutationsFilter setFilterValue={setFilterValue} value={mutationFilterValue} />
@@ -308,7 +373,7 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
308
373
  )}
309
374
  <CsvDownloadButton
310
375
  className='btn btn-xs'
311
- getData={() => getDownloadData(filteredData)}
376
+ getData={getDownloadDataAsync}
312
377
  filename='mutations_over_time.csv'
313
378
  />
314
379
  <MutationsOverTimeInfo originalComponentProps={originalComponentProps} />