@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.
- package/custom-elements.json +5 -5
- package/dist/components.d.ts +66 -67
- package/dist/components.js +457 -256
- package/dist/components.js.map +1 -1
- package/dist/util.d.ts +69 -66
- package/package.json +2 -9
- package/src/preact/MutationAnnotationsContext.tsx +1 -1
- package/src/preact/components/csv-download-button.tsx +22 -14
- package/src/preact/components/features-over-time-grid.tsx +189 -43
- package/src/preact/components/mutations-over-time-mutations-filter.stories.tsx +1 -1
- package/src/preact/components/mutations-over-time-mutations-filter.tsx +1 -1
- package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutationsByDay/aminoAcidMutationsOverTimePage1.json +52 -0
- package/src/preact/mutationsOverTime/__mockData__/byWeek/mutationsOverTimePage1.json +76 -0
- package/src/preact/mutationsOverTime/__mockData__/defaultMockData/mockDefaultMutationsOverTimeWithFilter.json +43 -0
- package/src/preact/mutationsOverTime/__mockData__/defaultMockData/mutationsOverTimePage1.json +126 -0
- package/src/preact/mutationsOverTime/__mockData__/defaultMockData/mutationsOverTimePage2.json +116 -0
- package/src/preact/mutationsOverTime/__mockData__/defaultMockData/mutationsOverTimePageSize20.json +216 -0
- package/src/preact/mutationsOverTime/getFilteredMutationCodes.spec.ts +236 -0
- package/src/preact/mutationsOverTime/{getFilteredMutationsOverTimeData.ts → getFilteredMutationCodes.ts} +32 -45
- package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +128 -23
- package/src/preact/mutationsOverTime/mutations-over-time.tsx +139 -74
- package/src/preact/mutationsOverTime/useMutationsOverTimePageData.ts +111 -0
- package/src/preact/queriesOverTime/queries-over-time-row-label-tooltip.stories.tsx +8 -10
- package/src/preact/queriesOverTime/queries-over-time-row-label-tooltip.tsx +7 -17
- package/src/preact/queriesOverTime/queries-over-time.tsx +1 -1
- package/src/preact/shared/tanstackTable/pagination-context.tsx +5 -2
- package/src/preact/shared/tanstackTable/pagination.tsx +11 -9
- package/src/preact/shared/tanstackTable/tanstackTable.tsx +7 -4
- package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.tsx +1 -1
- package/src/query/queryMutationsOverTime.spec.ts +187 -662
- package/src/query/queryMutationsOverTime.ts +46 -33
- package/src/utilEntrypoint.ts +1 -1
- package/src/utils/useControlledState.ts +15 -0
- package/src/web-components/gs-app.ts +2 -3
- package/src/web-components/input/gs-date-range-filter.tsx +2 -3
- package/src/web-components/input/gs-lineage-filter.tsx +2 -3
- package/src/web-components/input/gs-location-filter.tsx +2 -3
- package/src/web-components/input/gs-mutation-filter.tsx +3 -4
- package/src/web-components/input/gs-number-range-filter.tsx +2 -3
- package/src/web-components/input/gs-text-filter.tsx +2 -3
- package/src/web-components/mutation-annotations-context.ts +3 -1
- package/src/web-components/visualization/gs-aggregate.tsx +2 -3
- package/src/web-components/visualization/gs-genome-data-viewer.tsx +2 -3
- package/src/web-components/visualization/gs-mutation-comparison.tsx +2 -3
- package/src/web-components/visualization/gs-mutations-over-time.stories.ts +78 -22
- package/src/web-components/visualization/gs-mutations-over-time.tsx +2 -3
- package/src/web-components/visualization/gs-mutations.tsx +7 -8
- package/src/web-components/visualization/gs-number-sequences-over-time.tsx +2 -3
- package/src/web-components/visualization/gs-prevalence-over-time.tsx +2 -3
- package/src/web-components/visualization/gs-queries-over-time.tsx +2 -3
- package/src/web-components/visualization/gs-relative-growth-advantage.tsx +2 -3
- package/src/web-components/visualization/gs-sequences-by-location.tsx +2 -3
- package/src/web-components/visualization/gs-statistics.tsx +2 -3
- package/src/web-components/wastewaterVisualization/gs-wastewater-mutations-over-time.tsx +2 -3
- package/standalone-bundle/dashboard-components.js +6877 -6697
- package/standalone-bundle/dashboard-components.js.map +1 -1
- package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutationsByDay/aminoAcidMutationsOverTime.json +0 -5496
- package/src/preact/mutationsOverTime/__mockData__/byWeek/mutationsOverTime.json +0 -7100
- package/src/preact/mutationsOverTime/__mockData__/defaultMockData/mutationsOverTime.json +0 -12646
- 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
|
-
|
|
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
|
-
|
|
33
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
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:
|
|
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 = '
|
|
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('
|
|
283
|
-
|
|
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
|
|
452
|
-
await userEvent.click(
|
|
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
|
|
459
|
-
await userEvent.
|
|
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,
|
|
2
|
+
import { type Dispatch, type StateUpdater, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'preact/hooks';
|
|
3
3
|
import z from 'zod';
|
|
4
4
|
|
|
5
|
-
import {
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
type
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
} from '../../
|
|
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
|
|
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
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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 (
|
|
107
|
+
if (metadataLoading) {
|
|
95
108
|
return <LoadingDisplay />;
|
|
96
109
|
}
|
|
97
110
|
|
|
98
|
-
if (
|
|
99
|
-
throw
|
|
111
|
+
if (metadataError) {
|
|
112
|
+
throw metadataError;
|
|
100
113
|
}
|
|
101
114
|
|
|
102
|
-
if (
|
|
115
|
+
if (metadata.overallMutationData.length === 0) {
|
|
103
116
|
return <NoDataDisplay />;
|
|
104
117
|
}
|
|
105
118
|
|
|
106
|
-
const { overallMutationData, mutationOverTimeData } = data;
|
|
107
119
|
return (
|
|
108
|
-
<
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
132
|
+
metadata: MutationsOverTimeMetadata;
|
|
118
133
|
originalComponentProps: MutationsOverTimeProps;
|
|
119
|
-
|
|
134
|
+
pageIndex: number;
|
|
135
|
+
setPageIndex: Dispatch<StateUpdater<number>>;
|
|
120
136
|
};
|
|
121
137
|
|
|
122
138
|
const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
|
|
123
|
-
|
|
139
|
+
metadata,
|
|
124
140
|
originalComponentProps,
|
|
125
|
-
|
|
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
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
191
|
+
originalComponentProps.sequenceType,
|
|
162
192
|
mutationFilterValue,
|
|
163
|
-
sequenceType: originalComponentProps.sequenceType,
|
|
164
193
|
annotationProvider,
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
174
|
-
mutationFilterValue,
|
|
175
|
-
annotationProvider,
|
|
176
|
-
]);
|
|
216
|
+
);
|
|
177
217
|
|
|
178
|
-
const mutationRenderer: FeatureRenderer<Substitution | Deletion> =
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
<
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
<
|
|
240
|
+
<FeaturesOverTimeGridServerPaginated
|
|
198
241
|
rowLabelHeader='Mutation'
|
|
199
|
-
data={
|
|
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
|
-
<
|
|
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={
|
|
376
|
+
getData={getDownloadDataAsync}
|
|
312
377
|
filename='mutations_over_time.csv'
|
|
313
378
|
/>
|
|
314
379
|
<MutationsOverTimeInfo originalComponentProps={originalComponentProps} />
|