@genspectrum/dashboard-components 1.16.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 (36) hide show
  1. package/custom-elements.json +3 -3
  2. package/dist/components.d.ts +4 -4
  3. package/dist/components.js +449 -246
  4. package/dist/components.js.map +1 -1
  5. package/dist/util.d.ts +6 -6
  6. package/package.json +1 -1
  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} +29 -44
  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/shared/tanstackTable/pagination-context.tsx +5 -2
  24. package/src/preact/shared/tanstackTable/pagination.tsx +11 -9
  25. package/src/preact/shared/tanstackTable/tanstackTable.tsx +7 -4
  26. package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.tsx +1 -1
  27. package/src/query/queryMutationsOverTime.spec.ts +187 -662
  28. package/src/query/queryMutationsOverTime.ts +46 -33
  29. package/src/utils/useControlledState.ts +15 -0
  30. package/src/web-components/visualization/gs-mutations-over-time.stories.ts +78 -22
  31. package/standalone-bundle/dashboard-components.js +6872 -6690
  32. package/standalone-bundle/dashboard-components.js.map +1 -1
  33. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutationsByDay/aminoAcidMutationsOverTime.json +0 -5496
  34. package/src/preact/mutationsOverTime/__mockData__/byWeek/mutationsOverTime.json +0 -7100
  35. package/src/preact/mutationsOverTime/__mockData__/defaultMockData/mutationsOverTime.json +0 -12646
  36. package/src/preact/mutationsOverTime/getFilteredMutationsOverTime.spec.ts +0 -417
@@ -11,8 +11,8 @@ import {
11
11
  type TemporalGranularity,
12
12
  } from '../types';
13
13
  import { type Map2DContents } from '../utils/map2d';
14
- import { type Deletion, type Substitution, DeletionClass, SubstitutionClass } from '../utils/mutations';
15
- import { type Temporal } from '../utils/temporalClass';
14
+ import { type Deletion, DeletionClass, type Substitution, SubstitutionClass } from '../utils/mutations';
15
+ import { type Temporal, type TemporalClass } from '../utils/temporalClass';
16
16
 
17
17
  export type ProportionValue =
18
18
  | {
@@ -89,21 +89,19 @@ async function queryOverallMutationData({
89
89
  lapisFilter,
90
90
  sequenceType,
91
91
  lapis,
92
- granularity,
93
92
  lapisDateField,
94
93
  includeMutations,
94
+ requestedDateRanges,
95
95
  signal,
96
96
  }: {
97
97
  lapisFilter: LapisFilter;
98
98
  sequenceType: SequenceType;
99
99
  lapis: string;
100
- granularity: TemporalGranularity;
101
100
  lapisDateField: string;
102
101
  includeMutations?: string[];
102
+ requestedDateRanges: TemporalClass[];
103
103
  signal?: AbortSignal;
104
104
  }) {
105
- const requestedDateRanges = await queryDatesInDataset(lapisFilter, lapis, granularity, lapisDateField, signal);
106
-
107
105
  if (requestedDateRanges.length === 0) {
108
106
  if (includeMutations) {
109
107
  return {
@@ -142,7 +140,11 @@ async function queryOverallMutationData({
142
140
  return dataPromise;
143
141
  }
144
142
 
145
- export async function queryMutationsOverTimeData(
143
+ /**
144
+ * Phase 1: Fetch date buckets and the full sorted list of mutations above the threshold.
145
+ * This is cheap and does not depend on the current page.
146
+ */
147
+ export async function queryMutationsOverTimeMetadata(
146
148
  lapisFilter: LapisFilter,
147
149
  sequenceType: SequenceType,
148
150
  lapis: string,
@@ -168,12 +170,38 @@ export async function queryMutationsOverTimeData(
168
170
  lapis,
169
171
  lapisDateField,
170
172
  includeMutations: displayMutations,
171
- granularity,
173
+ requestedDateRanges,
174
+ signal,
172
175
  }).then((r) => r.content);
173
176
 
174
177
  overallMutationData.sort((a, b) => sortSubstitutionsAndDeletions(a.mutation, b.mutation));
175
178
 
176
- const includeMutations = overallMutationData.map((value) => value.mutation.code);
179
+ return { overallMutationData, requestedDateRanges };
180
+ }
181
+
182
+ export type MutationsOverTimeMetadata = Awaited<ReturnType<typeof queryMutationsOverTimeMetadata>>;
183
+
184
+ /**
185
+ * Phase 2: Fetch the time-series matrix for a specific slice of mutations (e.g. one page).
186
+ * Pass `requestedDateRanges` from Phase 1, plus a slice of mutation codes to fetch.
187
+ */
188
+ export async function queryMutationsOverTimePage(
189
+ lapisFilter: LapisFilter,
190
+ lapis: string,
191
+ lapisDateField: string,
192
+ sequenceType: SequenceType,
193
+ requestedDateRanges: MutationsOverTimeMetadata['requestedDateRanges'],
194
+ includeMutationCodes: string[],
195
+ signal?: AbortSignal,
196
+ ) {
197
+ if (includeMutationCodes.length === 0 || requestedDateRanges.length === 0) {
198
+ return new BaseMutationOverTimeDataMap({
199
+ keysFirstAxis: new Map(),
200
+ keysSecondAxis: new Map(requestedDateRanges.map((date) => [date.dateString, date])),
201
+ data: new Map(),
202
+ });
203
+ }
204
+
177
205
  const apiResult = await fetchMutationsOverTime(
178
206
  lapis,
179
207
  {
@@ -182,34 +210,22 @@ export async function queryMutationsOverTimeData(
182
210
  dateFrom: date.firstDay.toString(),
183
211
  dateTo: date.lastDay.toString(),
184
212
  })),
185
- includeMutations,
213
+ includeMutations: includeMutationCodes,
186
214
  dateField: lapisDateField,
187
215
  },
188
216
  sequenceType,
189
217
  signal,
190
218
  );
191
219
 
220
+ return buildMutationOverTimeDataMap(apiResult, requestedDateRanges);
221
+ }
222
+
223
+ function buildMutationOverTimeDataMap(
224
+ apiResult: Awaited<ReturnType<typeof fetchMutationsOverTime>>,
225
+ requestedDateRanges: MutationsOverTimeMetadata['requestedDateRanges'],
226
+ ) {
192
227
  const totalCounts = apiResult.data.totalCountsByDateRange;
193
228
  const responseMutations = apiResult.data.mutations.map(parseMutationCode);
194
- const mutationEntries: SubstitutionOrDeletionEntry[] = responseMutations.map((mutation, i) => {
195
- const numbers = {
196
- count: overallMutationData[i].count,
197
- proportion: overallMutationData[i].proportion,
198
- };
199
- if (mutation.type === 'deletion') {
200
- return {
201
- type: 'deletion',
202
- mutation,
203
- ...numbers,
204
- };
205
- } else {
206
- return {
207
- type: 'substitution',
208
- mutation,
209
- ...numbers,
210
- };
211
- }
212
- });
213
229
 
214
230
  const mutationOverTimeData: Map2DContents<Substitution | Deletion, Temporal, ProportionValue> = {
215
231
  keysFirstAxis: new Map(responseMutations.map((mutation) => [mutation.code, mutation])),
@@ -251,10 +267,7 @@ export async function queryMutationsOverTimeData(
251
267
  ),
252
268
  };
253
269
 
254
- return {
255
- mutationOverTimeData: new BaseMutationOverTimeDataMap(mutationOverTimeData),
256
- overallMutationData: mutationEntries,
257
- };
270
+ return new BaseMutationOverTimeDataMap(mutationOverTimeData);
258
271
  }
259
272
 
260
273
  function parseMutationCode(code: string): SubstitutionClass | DeletionClass {
@@ -0,0 +1,15 @@
1
+ import { useEffect, useState } from 'preact/hooks';
2
+
3
+ /**
4
+ * For when state is supplied via an attribute to the web component,
5
+ * and we need to update the internal state when the attribute value changes
6
+ */
7
+ export function useControlledState<S>(initialState: S) {
8
+ const [state, setState] = useState(initialState);
9
+
10
+ useEffect(() => {
11
+ setState(initialState);
12
+ }, [initialState]);
13
+
14
+ return [state, setState] as const;
15
+ }
@@ -6,10 +6,11 @@ import '../gs-app';
6
6
  import { withComponentDocs } from '../../../.storybook/ComponentDocsBlock';
7
7
  import { LAPIS_URL } from '../../constants';
8
8
  import mockAminoAcidMutationsByDayAminoAcidMutations from '../../preact/mutationsOverTime/__mockData__/aminoAcidMutationsByDay/aminoAcidMutations.json';
9
- import mockAminoAcidMutationsByDayAminoAcidMutationsOverTime from '../../preact/mutationsOverTime/__mockData__/aminoAcidMutationsByDay/aminoAcidMutationsOverTime.json';
10
- import mockByWeekMutationsOverTime from '../../preact/mutationsOverTime/__mockData__/byWeek/mutationsOverTime.json';
9
+ import mockAminoAcidMutationsByDayAminoAcidMutationsOverTimePage1 from '../../preact/mutationsOverTime/__mockData__/aminoAcidMutationsByDay/aminoAcidMutationsOverTimePage1.json';
10
+ import mockByWeekMutationsOverTimePage1 from '../../preact/mutationsOverTime/__mockData__/byWeek/mutationsOverTimePage1.json';
11
11
  import mockByWeekNucleotideMutations from '../../preact/mutationsOverTime/__mockData__/byWeek/nucleotideMutations.json';
12
- import mockDefaultMutationsOverTime from '../../preact/mutationsOverTime/__mockData__/defaultMockData/mutationsOverTime.json';
12
+ import mockDefaultMutationsOverTimePage1 from '../../preact/mutationsOverTime/__mockData__/defaultMockData/mutationsOverTimePage1.json';
13
+ import mockDefaultMutationsOverTimePageSize20 from '../../preact/mutationsOverTime/__mockData__/defaultMockData/mutationsOverTimePageSize20.json';
13
14
  import mockDefaultNucleotideMutations from '../../preact/mutationsOverTime/__mockData__/defaultMockData/nucleotideMutations.json';
14
15
  import mockWithDisplayMutationsMutationsOverTime from '../../preact/mutationsOverTime/__mockData__/withDisplayMutations/mutationsOverTime.json';
15
16
  import { type MutationsOverTimeProps } from '../../preact/mutationsOverTime/mutations-over-time';
@@ -91,11 +92,7 @@ const meta: Meta<Required<MutationsOverTimeProps>> = {
91
92
  matcher: {
92
93
  url: `${LAPIS_URL}/component/nucleotideMutationsOverTime`,
93
94
  body: {
94
- filters: {
95
- pangoLineage: 'JN.1*',
96
- dateFrom: '2024-01-15',
97
- dateTo: '2024-07-10',
98
- },
95
+ filters: { pangoLineage: 'JN.1*', dateFrom: '2024-01-15', dateTo: '2024-07-10' },
99
96
  dateRanges: [
100
97
  { dateFrom: '2024-01-01', dateTo: '2024-01-31' },
101
98
  { dateFrom: '2024-02-01', dateTo: '2024-02-29' },
@@ -105,12 +102,67 @@ const meta: Meta<Required<MutationsOverTimeProps>> = {
105
102
  { dateFrom: '2024-06-01', dateTo: '2024-06-30' },
106
103
  { dateFrom: '2024-07-01', dateTo: '2024-07-31' },
107
104
  ],
105
+ includeMutations: [
106
+ 'C44T',
107
+ 'C774T',
108
+ 'C7113T',
109
+ 'C12616T',
110
+ 'A13121T',
111
+ 'G15372T',
112
+ 'G17334T',
113
+ 'T18453C',
114
+ 'A19722G',
115
+ 'T21653-',
116
+ ],
117
+ dateField: 'date',
118
+ },
119
+ response: {
120
+ status: 200,
121
+ body: mockDefaultMutationsOverTimePage1,
122
+ },
123
+ },
124
+ },
125
+ {
126
+ matcher: {
127
+ name: 'downloadData',
128
+ url: `${LAPIS_URL}/component/nucleotideMutationsOverTime`,
129
+ body: {
130
+ filters: { pangoLineage: 'JN.1*', dateFrom: '2024-01-15', dateTo: '2024-07-10' },
131
+ dateRanges: [
132
+ { dateFrom: '2024-01-01', dateTo: '2024-01-31' },
133
+ { dateFrom: '2024-02-01', dateTo: '2024-02-29' },
134
+ { dateFrom: '2024-03-01', dateTo: '2024-03-31' },
135
+ { dateFrom: '2024-04-01', dateTo: '2024-04-30' },
136
+ { dateFrom: '2024-05-01', dateTo: '2024-05-31' },
137
+ { dateFrom: '2024-06-01', dateTo: '2024-06-30' },
138
+ { dateFrom: '2024-07-01', dateTo: '2024-07-31' },
139
+ ],
140
+ includeMutations: [
141
+ 'C44T',
142
+ 'C774T',
143
+ 'C7113T',
144
+ 'C12616T',
145
+ 'A13121T',
146
+ 'G15372T',
147
+ 'G17334T',
148
+ 'T18453C',
149
+ 'A19722G',
150
+ 'T21653-',
151
+ 'C21654-',
152
+ 'T21655-',
153
+ 'G22111T',
154
+ 'G22599C',
155
+ 'T22928C',
156
+ 'T23011-',
157
+ 'C23039G',
158
+ 'C23277T',
159
+ 'G24872T',
160
+ ],
108
161
  dateField: 'date',
109
162
  },
110
- matchPartialBody: true,
111
163
  response: {
112
164
  status: 200,
113
- body: mockDefaultMutationsOverTime,
165
+ body: mockDefaultMutationsOverTimePageSize20,
114
166
  },
115
167
  },
116
168
  },
@@ -310,23 +362,30 @@ export const ByWeek: StoryObj<Required<MutationsOverTimeProps>> = {
310
362
  matcher: {
311
363
  url: `${LAPIS_URL}/component/nucleotideMutationsOverTime`,
312
364
  body: {
313
- filters: {
314
- pangoLineage: 'JN.1*',
315
- dateFrom: '2024-01-15',
316
- dateTo: '2024-02-11',
317
- },
365
+ filters: { pangoLineage: 'JN.1*', dateFrom: '2024-01-15', dateTo: '2024-02-11' },
318
366
  dateRanges: [
319
367
  { dateFrom: '2024-01-15', dateTo: '2024-01-21' },
320
368
  { dateFrom: '2024-01-22', dateTo: '2024-01-28' },
321
369
  { dateFrom: '2024-01-29', dateTo: '2024-02-04' },
322
370
  { dateFrom: '2024-02-05', dateTo: '2024-02-11' },
323
371
  ],
372
+ includeMutations: [
373
+ 'C44T',
374
+ 'C774T',
375
+ 'C1762A',
376
+ 'C11747T',
377
+ 'G17562T',
378
+ 'T18453C',
379
+ 'G21641T',
380
+ 'C23277T',
381
+ 'C29870A',
382
+ ],
324
383
  dateField: 'date',
325
384
  },
326
385
  matchPartialBody: true,
327
386
  response: {
328
387
  status: 200,
329
- body: mockByWeekMutationsOverTime,
388
+ body: mockByWeekMutationsOverTimePage1,
330
389
  },
331
390
  },
332
391
  },
@@ -365,11 +424,7 @@ export const AminoAcidMutationsByDay: StoryObj<Required<MutationsOverTimeProps>>
365
424
  matcher: {
366
425
  url: `${LAPIS_URL}/component/aminoAcidMutationsOverTime`,
367
426
  body: {
368
- filters: {
369
- pangoLineage: 'JN.1*',
370
- dateFrom: '2024-01-20',
371
- dateTo: '2024-01-26',
372
- },
427
+ filters: { pangoLineage: 'JN.1*', dateFrom: '2024-01-20', dateTo: '2024-01-26' },
373
428
  dateRanges: [
374
429
  { dateFrom: '2024-01-20', dateTo: '2024-01-20' },
375
430
  { dateFrom: '2024-01-21', dateTo: '2024-01-21' },
@@ -379,12 +434,13 @@ export const AminoAcidMutationsByDay: StoryObj<Required<MutationsOverTimeProps>>
379
434
  { dateFrom: '2024-01-25', dateTo: '2024-01-25' },
380
435
  { dateFrom: '2024-01-26', dateTo: '2024-01-26' },
381
436
  ],
437
+ includeMutations: ['ORF1a:T170I', 'ORF1a:F499L', 'S:T572I'],
382
438
  dateField: 'date',
383
439
  },
384
440
  matchPartialBody: true,
385
441
  response: {
386
442
  status: 200,
387
- body: mockAminoAcidMutationsByDayAminoAcidMutationsOverTime,
443
+ body: mockAminoAcidMutationsByDayAminoAcidMutationsOverTimePage1,
388
444
  },
389
445
  },
390
446
  },