@genspectrum/dashboard-components 0.6.18 → 0.6.19

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 (108) hide show
  1. package/README.md +5 -12
  2. package/custom-elements.json +4 -4
  3. package/dist/assets/mutationOverTimeWorker-BdzqDqvO.js.map +1 -0
  4. package/dist/dashboard-components.js +216 -214
  5. package/dist/dashboard-components.js.map +1 -1
  6. package/dist/genspectrum-components.d.ts +40 -40
  7. package/dist/style.css +3 -2
  8. package/package.json +13 -2
  9. package/src/operator/FetchInsertionsOperator.ts +2 -2
  10. package/src/operator/FetchSubstitutionsOrDeletionsOperator.ts +3 -3
  11. package/src/preact/mutationComparison/fetchMutationData.spec.ts +3 -3
  12. package/src/preact/mutationComparison/getMutationComparisonTableData.spec.ts +11 -11
  13. package/src/preact/mutationComparison/getMutationComparisonTableData.ts +4 -4
  14. package/src/preact/mutationComparison/mutation-comparison-table.tsx +2 -2
  15. package/src/preact/mutationFilter/mutation-filter.tsx +27 -18
  16. package/src/preact/mutationFilter/parseAndValidateMutation.ts +4 -4
  17. package/src/preact/mutationFilter/parseMutation.spec.ts +17 -17
  18. package/src/preact/mutations/getInsertionsTableData.spec.ts +3 -3
  19. package/src/preact/mutations/getMutationsGridData.spec.ts +9 -9
  20. package/src/preact/mutations/getMutationsTableData.spec.ts +7 -7
  21. package/src/preact/mutations/mutations-insertions-table.tsx +3 -3
  22. package/src/preact/mutations/mutations-table.tsx +3 -3
  23. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutationsByDay.ts +45686 -0
  24. package/src/preact/mutationsOverTime/__mockData__/byWeek.ts +58989 -0
  25. package/src/preact/mutationsOverTime/__mockData__/defaultMockData.ts +103991 -0
  26. package/src/preact/mutationsOverTime/__mockData__/mockConversion.ts +54 -0
  27. package/src/preact/mutationsOverTime/__mockData__/showsMessageWhenTooManyMutations.ts +63690 -0
  28. package/src/preact/mutationsOverTime/getFilteredMutationsOverTime.spec.ts +176 -159
  29. package/src/preact/mutationsOverTime/getFilteredMutationsOverTimeData.ts +17 -59
  30. package/src/preact/mutationsOverTime/mutationOverTimeWorker.mock.ts +27 -0
  31. package/src/preact/mutationsOverTime/mutationOverTimeWorker.ts +29 -0
  32. package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +13 -14
  33. package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +9 -334
  34. package/src/preact/mutationsOverTime/mutations-over-time.tsx +68 -52
  35. package/src/preact/numberSequencesOverTime/getNumberOfSequencesOverTimeTableData.ts +3 -3
  36. package/src/preact/prevalenceOverTime/getPrevalenceOverTimeTableData.spec.ts +5 -5
  37. package/src/preact/prevalenceOverTime/prevalence-over-time-bubble-chart.tsx +1 -1
  38. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage-chart.tsx +2 -2
  39. package/src/preact/shared/sort/sortInsertions.spec.ts +11 -11
  40. package/src/preact/shared/sort/sortInsertions.ts +2 -2
  41. package/src/preact/shared/sort/sortSubstitutionsAndDeletions.spec.ts +13 -13
  42. package/src/preact/shared/sort/sortSubstitutionsAndDeletions.ts +7 -4
  43. package/src/preact/webWorkers/useWebWorker.ts +51 -0
  44. package/src/preact/webWorkers/workerFunction.ts +14 -0
  45. package/src/query/queryAggregatedDataOverTime.ts +3 -3
  46. package/src/query/queryMutationsOverTime.spec.ts +272 -51
  47. package/src/query/queryMutationsOverTime.ts +114 -45
  48. package/src/query/queryPrevalenceOverTime.ts +2 -2
  49. package/src/query/queryRelativeGrowthAdvantage.ts +3 -3
  50. package/src/types.ts +25 -5
  51. package/src/utils/map2d.spec.ts +29 -1
  52. package/src/utils/map2d.ts +22 -1
  53. package/src/utils/mutations.spec.ts +20 -20
  54. package/src/utils/mutations.ts +80 -17
  55. package/src/utils/sort.ts +5 -2
  56. package/src/utils/temporal.spec.ts +27 -24
  57. package/src/utils/{temporal.ts → temporalClass.ts} +170 -72
  58. package/src/utils/temporalTestHelpers.ts +3 -3
  59. package/src/web-components/introduction.mdx +46 -0
  60. package/src/web-components/visualization/gs-mutations-over-time.stories.ts +6 -699
  61. package/src/web-components/visualization/gs-mutations-over-time.tsx +2 -2
  62. package/standalone-bundle/dashboard-components.js +13763 -13754
  63. package/standalone-bundle/dashboard-components.js.map +1 -1
  64. package/src/preact/mutationsOverTime/__mockData__/aggregated_2024_01.json +0 -13
  65. package/src/preact/mutationsOverTime/__mockData__/aggregated_2024_02.json +0 -13
  66. package/src/preact/mutationsOverTime/__mockData__/aggregated_2024_03.json +0 -13
  67. package/src/preact/mutationsOverTime/__mockData__/aggregated_2024_04.json +0 -13
  68. package/src/preact/mutationsOverTime/__mockData__/aggregated_2024_05.json +0 -13
  69. package/src/preact/mutationsOverTime/__mockData__/aggregated_2024_06.json +0 -13
  70. package/src/preact/mutationsOverTime/__mockData__/aggregated_2024_07.json +0 -13
  71. package/src/preact/mutationsOverTime/__mockData__/aggregated_20_01_2024.json +0 -13
  72. package/src/preact/mutationsOverTime/__mockData__/aggregated_21_01_2024.json +0 -13
  73. package/src/preact/mutationsOverTime/__mockData__/aggregated_22_01_2024.json +0 -13
  74. package/src/preact/mutationsOverTime/__mockData__/aggregated_23_01_2024.json +0 -13
  75. package/src/preact/mutationsOverTime/__mockData__/aggregated_24_01_2024.json +0 -13
  76. package/src/preact/mutationsOverTime/__mockData__/aggregated_25_01_2024.json +0 -13
  77. package/src/preact/mutationsOverTime/__mockData__/aggregated_26_01_2024.json +0 -13
  78. package/src/preact/mutationsOverTime/__mockData__/aggregated_byDay.json +0 -38
  79. package/src/preact/mutationsOverTime/__mockData__/aggregated_byWeek.json +0 -122
  80. package/src/preact/mutationsOverTime/__mockData__/aggregated_date.json +0 -642
  81. package/src/preact/mutationsOverTime/__mockData__/aggregated_tooManyMutations.json +0 -1470
  82. package/src/preact/mutationsOverTime/__mockData__/aggregated_tooManyMutations_total.json +0 -13
  83. package/src/preact/mutationsOverTime/__mockData__/aggregated_week3_2024.json +0 -13
  84. package/src/preact/mutationsOverTime/__mockData__/aggregated_week4_2024.json +0 -13
  85. package/src/preact/mutationsOverTime/__mockData__/aggregated_week5_2024.json +0 -13
  86. package/src/preact/mutationsOverTime/__mockData__/aggregated_week6_2024.json +0 -13
  87. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutations_20_01_2024.json +0 -6778
  88. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutations_21_01_2024.json +0 -7129
  89. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutations_22_01_2024.json +0 -4681
  90. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutations_23_01_2024.json +0 -10738
  91. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutations_24_01_2024.json +0 -11710
  92. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutations_25_01_2024.json +0 -11557
  93. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutations_26_01_2024.json +0 -8596
  94. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutations_byDayOverall.json +0 -4726
  95. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_2024_01.json +0 -1747
  96. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_2024_02.json +0 -1774
  97. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_2024_03.json +0 -1819
  98. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_2024_04.json +0 -1864
  99. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_2024_05.json +0 -1927
  100. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_2024_06.json +0 -1864
  101. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_2024_07.json +0 -9
  102. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_byMonthOverall.json +0 -11143
  103. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_byWeekOverall.json +0 -9154
  104. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_tooManyMutations.json +0 -16453
  105. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_week3_2024.json +0 -8812
  106. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_week4_2024.json +0 -9730
  107. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_week5_2024.json +0 -9865
  108. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_week6_2024.json +0 -11314
@@ -4,7 +4,7 @@ import { queryMutationsOverTimeData } from './queryMutationsOverTime';
4
4
  import { DUMMY_LAPIS_URL, lapisRequestMocks } from '../../vitest.setup';
5
5
 
6
6
  describe('queryMutationsOverTime', () => {
7
- it('should fetch for a filter without date', async () => {
7
+ it('should fetch for a filter without date and sort by mutation and date', async () => {
8
8
  const lapisFilter = { field1: 'value1', field2: 'value2' };
9
9
  const dateField = 'dateField';
10
10
 
@@ -76,33 +76,79 @@ describe('queryMutationsOverTime', () => {
76
76
  },
77
77
  response: { data: [getSomeTestMutation(0.3, 3)] },
78
78
  },
79
+ {
80
+ body: {
81
+ ...lapisFilter,
82
+ dateFieldFrom: '2023-01-01',
83
+ dateFieldTo: '2023-01-03',
84
+ minProportion: 0.001,
85
+ },
86
+ response: {
87
+ data: [getSomeTestMutation(0.21, 6), getSomeOtherTestMutation(0.22, 4)],
88
+ },
89
+ },
79
90
  ],
80
91
  'nucleotide',
81
92
  );
82
93
 
83
- const result = await queryMutationsOverTimeData(lapisFilter, 'nucleotide', DUMMY_LAPIS_URL, dateField, 'day');
94
+ const { mutationOverTimeData, overallMutationData } = await queryMutationsOverTimeData({
95
+ lapisFilter,
96
+ sequenceType: 'nucleotide',
97
+ lapis: DUMMY_LAPIS_URL,
98
+ lapisDateField: dateField,
99
+ granularity: 'day',
100
+ });
84
101
 
85
- expect(result.getAsArray({ count: 0, proportion: 0, totalCount: 0 })).to.deep.equal([
86
- [
87
- { proportion: 0.1, count: 1, totalCount: 11 },
88
- { proportion: 0.2, count: 2, totalCount: 12 },
89
- { proportion: 0.3, count: 3, totalCount: 13 },
90
- ],
102
+ expect(mutationOverTimeData.getAsArray({ count: 0, proportion: 0, totalCount: 0 })).to.deep.equal([
91
103
  [
92
104
  { proportion: 0.4, count: 4, totalCount: 11 },
93
105
  { proportion: 0, count: 0, totalCount: 0 },
94
106
  { proportion: 0, count: 0, totalCount: 0 },
95
107
  ],
108
+ [
109
+ { proportion: 0.1, count: 1, totalCount: 11 },
110
+ { proportion: 0.2, count: 2, totalCount: 12 },
111
+ { proportion: 0.3, count: 3, totalCount: 13 },
112
+ ],
96
113
  ]);
97
114
 
98
- const sequences = result.getFirstAxisKeys();
99
- expect(sequences[0].code).toBe('sequenceName:A123T');
100
- expect(sequences[1].code).toBe('otherSequenceName:G234C');
115
+ const sequences = mutationOverTimeData.getFirstAxisKeys();
116
+ expect(sequences[0].code).toBe('otherSequenceName:G234C');
117
+ expect(sequences[1].code).toBe('sequenceName:A123T');
118
+
119
+ const dates = mutationOverTimeData.getSecondAxisKeys();
120
+ expect(dates[0].dateString).toBe('2023-01-01');
121
+ expect(dates[1].dateString).toBe('2023-01-02');
122
+ expect(dates[2].dateString).toBe('2023-01-03');
101
123
 
102
- const dates = result.getSecondAxisKeys();
103
- expect(dates[0].toString()).toBe('2023-01-01');
104
- expect(dates[1].toString()).toBe('2023-01-02');
105
- expect(dates[2].toString()).toBe('2023-01-03');
124
+ expect(overallMutationData).to.deep.equal([
125
+ {
126
+ type: 'substitution',
127
+ mutation: {
128
+ valueAtReference: 'G',
129
+ substitutionValue: 'C',
130
+ position: 234,
131
+ segment: 'otherSequenceName',
132
+ code: 'otherSequenceName:G234C',
133
+ type: 'substitution',
134
+ },
135
+ count: 4,
136
+ proportion: 0.22,
137
+ },
138
+ {
139
+ type: 'substitution',
140
+ mutation: {
141
+ valueAtReference: 'A',
142
+ substitutionValue: 'T',
143
+ position: 123,
144
+ segment: 'sequenceName',
145
+ code: 'sequenceName:A123T',
146
+ type: 'substitution',
147
+ },
148
+ count: 6,
149
+ proportion: 0.21,
150
+ },
151
+ ]);
106
152
  });
107
153
 
108
154
  it('should fetch for dates with no mutations', async () => {
@@ -177,33 +223,54 @@ describe('queryMutationsOverTime', () => {
177
223
  },
178
224
  response: { data: [getSomeTestMutation(0.3, 3)] },
179
225
  },
226
+ {
227
+ body: {
228
+ ...lapisFilter,
229
+ dateFieldFrom: '2023-01-01',
230
+ dateFieldTo: '2023-01-03',
231
+ minProportion: 0.001,
232
+ },
233
+ response: {
234
+ data: [
235
+ getSomeTestMutation(0.1, 1),
236
+ getSomeTestMutation(0.3, 3),
237
+ getSomeOtherTestMutation(0.4, 4),
238
+ ],
239
+ },
240
+ },
180
241
  ],
181
242
  'nucleotide',
182
243
  );
183
244
 
184
- const result = await queryMutationsOverTimeData(lapisFilter, 'nucleotide', DUMMY_LAPIS_URL, dateField, 'day');
245
+ const { mutationOverTimeData } = await queryMutationsOverTimeData({
246
+ lapisFilter,
247
+ sequenceType: 'nucleotide',
248
+ lapis: DUMMY_LAPIS_URL,
249
+ lapisDateField: dateField,
250
+ granularity: 'day',
251
+ });
185
252
 
186
- expect(result.getAsArray({ count: 0, proportion: 0, totalCount: 0 })).to.deep.equal([
253
+ expect(mutationOverTimeData.getAsArray({ count: 0, proportion: 0, totalCount: 0 })).to.deep.equal([
187
254
  [
188
- { proportion: 0.1, count: 1, totalCount: 11 },
189
- { proportion: 0.3, count: 3, totalCount: 13 },
255
+ { proportion: 0.4, count: 4, totalCount: 11 },
256
+ { proportion: 0, count: 0, totalCount: 0 },
190
257
  { proportion: 0, count: 0, totalCount: 0 },
191
258
  ],
192
259
  [
193
- { proportion: 0.4, count: 4, totalCount: 11 },
194
- { proportion: 0, count: 0, totalCount: 0 },
260
+ { proportion: 0.1, count: 1, totalCount: 11 },
195
261
  { proportion: 0, count: 0, totalCount: 0 },
262
+ { proportion: 0.3, count: 3, totalCount: 13 },
196
263
  ],
197
264
  ]);
198
265
 
199
- const sequences = result.getFirstAxisKeys();
200
- expect(sequences[0].code).toBe('sequenceName:A123T');
201
- expect(sequences[1].code).toBe('otherSequenceName:G234C');
266
+ const sequences = mutationOverTimeData.getFirstAxisKeys();
267
+ expect(sequences[0].code).toBe('otherSequenceName:G234C');
268
+ expect(sequences[1].code).toBe('sequenceName:A123T');
202
269
 
203
- const dates = result.getSecondAxisKeys();
204
- expect(dates[0].toString()).toBe('2023-01-01');
205
- expect(dates[1].toString()).toBe('2023-01-03');
206
- expect(dates[2].toString()).toBe('2023-01-02');
270
+ const dates = mutationOverTimeData.getSecondAxisKeys();
271
+ expect(dates[0].dateString).toBe('2023-01-01');
272
+ expect(dates[1].dateString).toBe('2023-01-02');
273
+ expect(dates[2].dateString).toBe('2023-01-03');
207
274
  });
208
275
 
209
276
  it('should return empty map when no mutations are found', async () => {
@@ -278,15 +345,32 @@ describe('queryMutationsOverTime', () => {
278
345
  },
279
346
  response: { data: [] },
280
347
  },
348
+ {
349
+ body: {
350
+ ...lapisFilter,
351
+ dateFieldFrom: '2023-01-01',
352
+ dateFieldTo: '2023-01-03',
353
+ minProportion: 0.001,
354
+ },
355
+ response: {
356
+ data: [],
357
+ },
358
+ },
281
359
  ],
282
360
  'nucleotide',
283
361
  );
284
362
 
285
- const result = await queryMutationsOverTimeData(lapisFilter, 'nucleotide', DUMMY_LAPIS_URL, dateField, 'day');
286
-
287
- expect(result.getAsArray({ count: 0, proportion: 0, totalCount: 0 })).to.deep.equal([]);
288
- expect(result.getFirstAxisKeys()).to.deep.equal([]);
289
- expect(result.getSecondAxisKeys()).to.deep.equal([]);
363
+ const { mutationOverTimeData } = await queryMutationsOverTimeData({
364
+ lapisFilter,
365
+ sequenceType: 'nucleotide',
366
+ lapis: DUMMY_LAPIS_URL,
367
+ lapisDateField: dateField,
368
+ granularity: 'day',
369
+ });
370
+
371
+ expect(mutationOverTimeData.getAsArray({ count: 0, proportion: 0, totalCount: 0 })).to.deep.equal([]);
372
+ expect(mutationOverTimeData.getFirstAxisKeys()).to.deep.equal([]);
373
+ expect(mutationOverTimeData.getSecondAxisKeys()).to.deep.equal([]);
290
374
  });
291
375
 
292
376
  it('should use dateFrom from filter', async () => {
@@ -343,25 +427,42 @@ describe('queryMutationsOverTime', () => {
343
427
  },
344
428
  response: { data: [getSomeTestMutation(0.3, 3)] },
345
429
  },
430
+ {
431
+ body: {
432
+ ...lapisFilter,
433
+ dateFieldFrom: '2023-01-02',
434
+ dateFieldTo: '2023-01-03',
435
+ minProportion: 0.001,
436
+ },
437
+ response: {
438
+ data: [getSomeTestMutation(0.2, 2), getSomeTestMutation(0.3, 3)],
439
+ },
440
+ },
346
441
  ],
347
442
  'nucleotide',
348
443
  );
349
444
 
350
- const result = await queryMutationsOverTimeData(lapisFilter, 'nucleotide', DUMMY_LAPIS_URL, dateField, 'day');
445
+ const { mutationOverTimeData } = await queryMutationsOverTimeData({
446
+ lapisFilter,
447
+ sequenceType: 'nucleotide',
448
+ lapis: DUMMY_LAPIS_URL,
449
+ lapisDateField: dateField,
450
+ granularity: 'day',
451
+ });
351
452
 
352
- expect(result.getAsArray({ count: 0, proportion: 0, totalCount: 0 })).to.deep.equal([
453
+ expect(mutationOverTimeData.getAsArray({ count: 0, proportion: 0, totalCount: 0 })).to.deep.equal([
353
454
  [
354
455
  { proportion: 0.2, count: 2, totalCount: 11 },
355
456
  { proportion: 0.3, count: 3, totalCount: 12 },
356
457
  ],
357
458
  ]);
358
459
 
359
- const sequences = result.getFirstAxisKeys();
460
+ const sequences = mutationOverTimeData.getFirstAxisKeys();
360
461
  expect(sequences[0].code).toBe('sequenceName:A123T');
361
462
 
362
- const dates = result.getSecondAxisKeys();
363
- expect(dates[0].toString()).toBe('2023-01-02');
364
- expect(dates[1].toString()).toBe('2023-01-03');
463
+ const dates = mutationOverTimeData.getSecondAxisKeys();
464
+ expect(dates[0].dateString).toBe('2023-01-02');
465
+ expect(dates[1].dateString).toBe('2023-01-03');
365
466
  });
366
467
 
367
468
  it('should use dateTo from filter', async () => {
@@ -418,25 +519,42 @@ describe('queryMutationsOverTime', () => {
418
519
  },
419
520
  response: { data: [getSomeTestMutation(0.2, 2)] },
420
521
  },
522
+ {
523
+ body: {
524
+ ...lapisFilter,
525
+ dateFieldFrom: '2023-01-01',
526
+ dateFieldTo: '2023-01-02',
527
+ minProportion: 0.001,
528
+ },
529
+ response: {
530
+ data: [getSomeTestMutation(0.1, 1), getSomeTestMutation(0.2, 2)],
531
+ },
532
+ },
421
533
  ],
422
534
  'nucleotide',
423
535
  );
424
536
 
425
- const result = await queryMutationsOverTimeData(lapisFilter, 'nucleotide', DUMMY_LAPIS_URL, dateField, 'day');
537
+ const { mutationOverTimeData } = await queryMutationsOverTimeData({
538
+ lapisFilter,
539
+ sequenceType: 'nucleotide',
540
+ lapis: DUMMY_LAPIS_URL,
541
+ lapisDateField: dateField,
542
+ granularity: 'day',
543
+ });
426
544
 
427
- expect(result.getAsArray({ count: 0, proportion: 0, totalCount: 0 })).to.deep.equal([
545
+ expect(mutationOverTimeData.getAsArray({ count: 0, proportion: 0, totalCount: 0 })).to.deep.equal([
428
546
  [
429
547
  { proportion: 0.1, count: 1, totalCount: 11 },
430
548
  { proportion: 0.2, count: 2, totalCount: 12 },
431
549
  ],
432
550
  ]);
433
551
 
434
- const sequences = result.getFirstAxisKeys();
552
+ const sequences = mutationOverTimeData.getFirstAxisKeys();
435
553
  expect(sequences[0].code).toBe('sequenceName:A123T');
436
554
 
437
- const dates = result.getSecondAxisKeys();
438
- expect(dates[0].toString()).toBe('2023-01-01');
439
- expect(dates[1].toString()).toBe('2023-01-02');
555
+ const dates = mutationOverTimeData.getSecondAxisKeys();
556
+ expect(dates[0].dateString).toBe('2023-01-01');
557
+ expect(dates[1].dateString).toBe('2023-01-02');
440
558
  });
441
559
 
442
560
  it('should use date from filter', async () => {
@@ -479,9 +597,15 @@ describe('queryMutationsOverTime', () => {
479
597
  'nucleotide',
480
598
  );
481
599
 
482
- const result = await queryMutationsOverTimeData(lapisFilter, 'nucleotide', DUMMY_LAPIS_URL, dateField, 'day');
600
+ const { mutationOverTimeData } = await queryMutationsOverTimeData({
601
+ lapisFilter,
602
+ sequenceType: 'nucleotide',
603
+ lapis: DUMMY_LAPIS_URL,
604
+ lapisDateField: dateField,
605
+ granularity: 'day',
606
+ });
483
607
 
484
- expect(result.getAsArray({ count: 0, proportion: 0, totalCount: 0 })).to.deep.equal([
608
+ expect(mutationOverTimeData.getAsArray({ count: 0, proportion: 0, totalCount: 0 })).to.deep.equal([
485
609
  [
486
610
  {
487
611
  proportion: 0.2,
@@ -491,11 +615,108 @@ describe('queryMutationsOverTime', () => {
491
615
  ],
492
616
  ]);
493
617
 
494
- const sequences = result.getFirstAxisKeys();
618
+ const sequences = mutationOverTimeData.getFirstAxisKeys();
495
619
  expect(sequences[0].code).toBe('sequenceName:A123T');
496
620
 
497
- const dates = result.getSecondAxisKeys();
498
- expect(dates[0].toString()).toBe('2023-01-02');
621
+ const dates = mutationOverTimeData.getSecondAxisKeys();
622
+ expect(dates[0].dateString).toBe('2023-01-02');
623
+ });
624
+
625
+ it('should fetch data including the first and last day of the granularity', async () => {
626
+ const lapisFilter = { field1: 'value1', field2: 'value2' };
627
+ const dateField = 'dateField';
628
+
629
+ lapisRequestMocks.multipleAggregated([
630
+ {
631
+ body: { ...lapisFilter, fields: [dateField] },
632
+ response: {
633
+ data: [
634
+ { count: 1, [dateField]: '2023-01-05' },
635
+ { count: 2, [dateField]: '2023-02-15' },
636
+ ],
637
+ },
638
+ },
639
+ {
640
+ body: {
641
+ ...lapisFilter,
642
+ dateFieldFrom: '2023-01-01',
643
+ dateFieldTo: '2023-01-31',
644
+ fields: [],
645
+ },
646
+ response: { data: [{ count: 11 }] },
647
+ },
648
+ {
649
+ body: {
650
+ ...lapisFilter,
651
+ dateFieldFrom: '2023-02-01',
652
+ dateFieldTo: '2023-02-28',
653
+ fields: [],
654
+ },
655
+ response: { data: [{ count: 12 }] },
656
+ },
657
+ ]);
658
+
659
+ lapisRequestMocks.multipleMutations(
660
+ [
661
+ {
662
+ body: {
663
+ ...lapisFilter,
664
+ dateFieldFrom: '2023-01-01',
665
+ dateFieldTo: '2023-01-31',
666
+ minProportion: 0.001,
667
+ },
668
+ response: { data: [getSomeTestMutation(0.1, 1), getSomeOtherTestMutation(0.4, 4)] },
669
+ },
670
+ {
671
+ body: {
672
+ ...lapisFilter,
673
+ dateFieldFrom: '2023-02-01',
674
+ dateFieldTo: '2023-02-28',
675
+ minProportion: 0.001,
676
+ },
677
+ response: { data: [getSomeTestMutation(0.2, 2)] },
678
+ },
679
+ {
680
+ body: {
681
+ ...lapisFilter,
682
+ dateFieldFrom: '2023-01-01',
683
+ dateFieldTo: '2023-02-28',
684
+ minProportion: 0.001,
685
+ },
686
+ response: {
687
+ data: [getSomeTestMutation(0.21, 6), getSomeOtherTestMutation(0.22, 4)],
688
+ },
689
+ },
690
+ ],
691
+ 'nucleotide',
692
+ );
693
+
694
+ const { mutationOverTimeData } = await queryMutationsOverTimeData({
695
+ lapisFilter,
696
+ sequenceType: 'nucleotide',
697
+ lapis: DUMMY_LAPIS_URL,
698
+ lapisDateField: dateField,
699
+ granularity: 'month',
700
+ });
701
+
702
+ expect(mutationOverTimeData.getAsArray({ count: 0, proportion: 0, totalCount: 0 })).to.deep.equal([
703
+ [
704
+ { proportion: 0.4, count: 4, totalCount: 11 },
705
+ { proportion: 0, count: 0, totalCount: 0 },
706
+ ],
707
+ [
708
+ { proportion: 0.1, count: 1, totalCount: 11 },
709
+ { proportion: 0.2, count: 2, totalCount: 12 },
710
+ ],
711
+ ]);
712
+
713
+ const sequences = mutationOverTimeData.getFirstAxisKeys();
714
+ expect(sequences[0].code).toBe('otherSequenceName:G234C');
715
+ expect(sequences[1].code).toBe('sequenceName:A123T');
716
+
717
+ const dates = mutationOverTimeData.getSecondAxisKeys();
718
+ expect(dates[0].dateString).toBe('2023-01');
719
+ expect(dates[1].dateString).toBe('2023-02');
499
720
  });
500
721
 
501
722
  function getSomeTestMutation(proportion: number, count: number) {
@@ -512,7 +733,7 @@ describe('queryMutationsOverTime', () => {
512
733
 
513
734
  function getSomeOtherTestMutation(proportion: number, count: number) {
514
735
  return {
515
- mutation: 'otherSequenceName:A123T',
736
+ mutation: 'otherSequenceName:G234C',
516
737
  proportion,
517
738
  count,
518
739
  sequenceName: 'otherSequenceName',
@@ -6,54 +6,91 @@ import { MapOperator } from '../operator/MapOperator';
6
6
  import { RenameFieldOperator } from '../operator/RenameFieldOperator';
7
7
  import { SortOperator } from '../operator/SortOperator';
8
8
  import { UserFacingError } from '../preact/components/error-display';
9
+ import { sortSubstitutionsAndDeletions } from '../preact/shared/sort/sortSubstitutionsAndDeletions';
9
10
  import {
11
+ type DeletionEntry,
10
12
  type LapisFilter,
11
13
  type SequenceType,
14
+ type SubstitutionEntry,
12
15
  type SubstitutionOrDeletionEntry,
13
16
  type TemporalGranularity,
14
17
  } from '../types';
15
- import { Map2dBase, type Map2d } from '../utils/map2d';
16
- import { type Deletion, type Substitution } from '../utils/mutations';
18
+ import { type Map2d, Map2dBase } from '../utils/map2d';
17
19
  import {
20
+ type Deletion,
21
+ type DeletionClass,
22
+ type Substitution,
23
+ type SubstitutionClass,
24
+ toSubstitutionOrDeletion,
25
+ } from '../utils/mutations';
26
+ import {
27
+ compareTemporal,
18
28
  dateRangeCompare,
19
29
  generateAllInRange,
20
30
  getMinMaxTemporal,
21
31
  parseDateStringToTemporal,
22
32
  type Temporal,
23
- } from '../utils/temporal';
33
+ type TemporalClass,
34
+ toTemporal,
35
+ } from '../utils/temporalClass';
24
36
 
25
37
  export type MutationOverTimeData = {
26
- date: Temporal;
38
+ date: TemporalClass;
27
39
  mutations: SubstitutionOrDeletionEntry[];
28
40
  totalCount: number;
29
41
  };
30
42
 
31
43
  export type MutationOverTimeMutationValue = { proportion: number; count: number; totalCount: number };
32
44
  export type MutationOverTimeDataGroupedByMutation = Map2d<
33
- Substitution | Deletion,
34
- Temporal,
45
+ SubstitutionClass | DeletionClass,
46
+ TemporalClass,
35
47
  MutationOverTimeMutationValue
36
48
  >;
37
49
 
38
50
  const MAX_NUMBER_OF_GRID_COLUMNS = 200;
39
51
 
40
- export async function queryOverallMutationData(
41
- lapisFilter: LapisFilter,
42
- sequenceType: SequenceType,
43
- lapis: string,
44
- signal?: AbortSignal,
45
- ) {
46
- return fetchAndPrepareSubstitutionsOrDeletions(lapisFilter, sequenceType).evaluate(lapis, signal);
52
+ export async function queryOverallMutationData({
53
+ lapisFilter,
54
+ sequenceType,
55
+ lapis,
56
+ granularity,
57
+ lapisDateField,
58
+ signal,
59
+ }: {
60
+ lapisFilter: LapisFilter;
61
+ sequenceType: SequenceType;
62
+ lapis: string;
63
+ granularity: TemporalGranularity;
64
+ lapisDateField: string;
65
+ signal?: AbortSignal;
66
+ }) {
67
+ const allDates = await getDatesInDataset(lapisFilter, lapis, granularity, lapisDateField, signal);
68
+ const filter = {
69
+ ...lapisFilter,
70
+ [`${lapisDateField}From`]: allDates[0].firstDay.toString(),
71
+ [`${lapisDateField}To`]: allDates[allDates.length - 1].lastDay.toString(),
72
+ };
73
+
74
+ return fetchAndPrepareSubstitutionsOrDeletions(filter, sequenceType).evaluate(lapis, signal);
47
75
  }
48
76
 
49
- export async function queryMutationsOverTimeData(
50
- lapisFilter: LapisFilter,
51
- sequenceType: SequenceType,
52
- lapis: string,
53
- lapisDateField: string,
54
- granularity: TemporalGranularity,
55
- signal?: AbortSignal,
56
- ) {
77
+ export type MutationOverTimeQuery = {
78
+ lapisFilter: LapisFilter;
79
+ sequenceType: SequenceType;
80
+ lapis: string;
81
+ lapisDateField: string;
82
+ granularity: TemporalGranularity;
83
+ signal?: AbortSignal;
84
+ };
85
+
86
+ export async function queryMutationsOverTimeData({
87
+ lapisFilter,
88
+ sequenceType,
89
+ lapis,
90
+ lapisDateField,
91
+ granularity,
92
+ signal,
93
+ }: MutationOverTimeQuery) {
57
94
  const allDates = await getDatesInDataset(lapisFilter, lapis, granularity, lapisDateField, signal);
58
95
 
59
96
  if (allDates.length > MAX_NUMBER_OF_GRID_COLUMNS) {
@@ -86,7 +123,20 @@ export async function queryMutationsOverTimeData(
86
123
 
87
124
  const data = await Promise.all(subQueries);
88
125
 
89
- return groupByMutation(data);
126
+ const overallMutationsData = (
127
+ await queryOverallMutationData({
128
+ lapisFilter,
129
+ sequenceType,
130
+ lapis,
131
+ lapisDateField,
132
+ granularity,
133
+ })
134
+ ).content;
135
+
136
+ return {
137
+ mutationOverTimeData: groupByMutation(data, overallMutationsData),
138
+ overallMutationData: overallMutationsData,
139
+ };
90
140
  }
91
141
 
92
142
  async function getDatesInDataset(
@@ -156,39 +206,58 @@ function fetchAndPrepareSubstitutionsOrDeletions(filter: LapisFilter, sequenceTy
156
206
  return new FetchSubstitutionsOrDeletionsOperator(filter, sequenceType, 0.001);
157
207
  }
158
208
 
159
- export function groupByMutation(data: MutationOverTimeData[]) {
209
+ export function serializeSubstitutionOrDeletion(mutation: Substitution | Deletion) {
210
+ return mutation.code;
211
+ }
212
+
213
+ export function serializeTemporal(date: Temporal) {
214
+ return date.dateString;
215
+ }
216
+
217
+ export function groupByMutation(
218
+ data: MutationOverTimeData[],
219
+ overallMutationData: (SubstitutionEntry | DeletionEntry)[],
220
+ ) {
160
221
  const dataArray = new Map2dBase<Substitution | Deletion, Temporal, MutationOverTimeMutationValue>(
161
- (mutation) => mutation.code,
162
- (date) => date.toString(),
222
+ serializeSubstitutionOrDeletion,
223
+ serializeTemporal,
163
224
  );
164
225
 
165
- data.forEach((mutationData) => {
166
- mutationData.mutations.forEach((mutationEntry) => {
167
- dataArray.set(mutationEntry.mutation, mutationData.date, {
168
- count: mutationEntry.count,
169
- proportion: mutationEntry.proportion,
170
- totalCount: mutationData.totalCount,
226
+ const allDates = data.map((mutationData) => mutationData.date);
227
+
228
+ const sortedOverallMutationData = overallMutationData
229
+ .sort((a, b) => sortSubstitutionsAndDeletions(a.mutation, b.mutation))
230
+ .map((entry) => {
231
+ return toSubstitutionOrDeletion(entry.mutation);
232
+ });
233
+ const sortedDates = allDates.sort((a, b) => compareTemporal(a, b)).map((date) => toTemporal(date));
234
+
235
+ sortedOverallMutationData.forEach((mutationData) => {
236
+ sortedDates.forEach((date) => {
237
+ dataArray.set(mutationData, date, {
238
+ count: 0,
239
+ proportion: 0,
240
+ totalCount: 0,
171
241
  });
172
242
  });
173
243
  });
174
244
 
175
- addZeroValuesForDatesWithNoMutationData(dataArray, data);
176
-
177
- return dataArray;
178
- }
245
+ data.forEach((mutationData) => {
246
+ mutationData.mutations.forEach((mutationEntry) => {
247
+ const mutation = toSubstitutionOrDeletion(mutationEntry.mutation);
248
+ const date = toTemporal(mutationData.date);
179
249
 
180
- function addZeroValuesForDatesWithNoMutationData(
181
- dataArray: Map2dBase<Substitution | Deletion, Temporal, MutationOverTimeMutationValue>,
182
- data: MutationOverTimeData[],
183
- ) {
184
- if (dataArray.getFirstAxisKeys().length !== 0) {
185
- const someMutation = dataArray.getFirstAxisKeys()[0];
186
- data.forEach((mutationData) => {
187
- if (mutationData.mutations.length === 0) {
188
- dataArray.set(someMutation, mutationData.date, { count: 0, proportion: 0, totalCount: 0 });
250
+ if (dataArray.get(mutation, date) !== undefined) {
251
+ dataArray.set(mutation, date, {
252
+ count: mutationEntry.count,
253
+ proportion: mutationEntry.proportion,
254
+ totalCount: mutationData.totalCount,
255
+ });
189
256
  }
190
257
  });
191
- }
258
+ });
259
+
260
+ return dataArray;
192
261
  }
193
262
 
194
263
  function getTotalNumberOfSequencesInDateRange(filter: LapisFilter) {
@@ -1,7 +1,7 @@
1
1
  import { queryAggregatedDataOverTime } from './queryAggregatedDataOverTime';
2
2
  import { DivisionOperator } from '../operator/DivisionOperator';
3
3
  import { type LapisFilter, type NamedLapisFilter, type TemporalGranularity } from '../types';
4
- import { type Temporal } from '../utils/temporal';
4
+ import { type TemporalClass } from '../utils/temporalClass';
5
5
  import { makeArray } from '../utils/utils';
6
6
 
7
7
  export type PrevalenceOverTimeData = PrevalenceOverTimeVariantData[];
@@ -15,7 +15,7 @@ export type PrevalenceOverTimeVariantDataPoint = {
15
15
  count: number;
16
16
  prevalence: number;
17
17
  total: number;
18
- dateRange: Temporal | null;
18
+ dateRange: TemporalClass | null;
19
19
  };
20
20
 
21
21
  export function queryPrevalenceOverTime(