@genspectrum/dashboard-components 0.6.18 → 0.7.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 (118) hide show
  1. package/README.md +5 -12
  2. package/custom-elements.json +22 -22
  3. package/dist/assets/mutationOverTimeWorker-BOCXtKzd.js.map +1 -0
  4. package/dist/dashboard-components.js +301 -302
  5. package/dist/dashboard-components.js.map +1 -1
  6. package/dist/genspectrum-components.d.ts +60 -10
  7. package/dist/style.css +3 -2
  8. package/package.json +13 -4
  9. package/src/index.ts +1 -0
  10. package/src/operator/FetchInsertionsOperator.ts +2 -2
  11. package/src/operator/FetchSubstitutionsOrDeletionsOperator.ts +3 -3
  12. package/src/preact/dateRangeSelector/computeInitialValues.spec.ts +53 -38
  13. package/src/preact/dateRangeSelector/computeInitialValues.ts +17 -23
  14. package/src/preact/dateRangeSelector/date-range-selector.stories.tsx +46 -32
  15. package/src/preact/dateRangeSelector/date-range-selector.tsx +24 -26
  16. package/src/preact/dateRangeSelector/dateRangeOption.ts +65 -0
  17. package/src/preact/dateRangeSelector/selectableOptions.ts +17 -66
  18. package/src/preact/mutationComparison/fetchMutationData.spec.ts +3 -3
  19. package/src/preact/mutationComparison/getMutationComparisonTableData.spec.ts +11 -11
  20. package/src/preact/mutationComparison/getMutationComparisonTableData.ts +4 -4
  21. package/src/preact/mutationComparison/mutation-comparison-table.tsx +2 -2
  22. package/src/preact/mutationFilter/mutation-filter.tsx +27 -18
  23. package/src/preact/mutationFilter/parseAndValidateMutation.ts +4 -4
  24. package/src/preact/mutationFilter/parseMutation.spec.ts +17 -17
  25. package/src/preact/mutations/getInsertionsTableData.spec.ts +3 -3
  26. package/src/preact/mutations/getMutationsGridData.spec.ts +9 -9
  27. package/src/preact/mutations/getMutationsTableData.spec.ts +7 -7
  28. package/src/preact/mutations/mutations-insertions-table.tsx +3 -3
  29. package/src/preact/mutations/mutations-table.tsx +3 -3
  30. package/src/preact/mutationsOverTime/MutationOverTimeData.ts +20 -0
  31. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutationsByDay.ts +45686 -0
  32. package/src/preact/mutationsOverTime/__mockData__/byWeek.ts +58989 -0
  33. package/src/preact/mutationsOverTime/__mockData__/defaultMockData.ts +103991 -0
  34. package/src/preact/mutationsOverTime/__mockData__/mockConversion.ts +54 -0
  35. package/src/preact/mutationsOverTime/__mockData__/showsMessageWhenTooManyMutations.ts +63690 -0
  36. package/src/preact/mutationsOverTime/getFilteredMutationsOverTime.spec.ts +177 -161
  37. package/src/preact/mutationsOverTime/getFilteredMutationsOverTimeData.ts +17 -59
  38. package/src/preact/mutationsOverTime/mutationOverTimeWorker.mock.ts +27 -0
  39. package/src/preact/mutationsOverTime/mutationOverTimeWorker.ts +29 -0
  40. package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +13 -14
  41. package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +9 -334
  42. package/src/preact/mutationsOverTime/mutations-over-time.tsx +59 -54
  43. package/src/preact/numberSequencesOverTime/getNumberOfSequencesOverTimeTableData.ts +3 -3
  44. package/src/preact/prevalenceOverTime/getPrevalenceOverTimeTableData.spec.ts +5 -5
  45. package/src/preact/prevalenceOverTime/prevalence-over-time-bubble-chart.tsx +1 -1
  46. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage-chart.tsx +2 -2
  47. package/src/preact/shared/sort/sortInsertions.spec.ts +11 -11
  48. package/src/preact/shared/sort/sortInsertions.ts +2 -2
  49. package/src/preact/shared/sort/sortSubstitutionsAndDeletions.spec.ts +13 -13
  50. package/src/preact/shared/sort/sortSubstitutionsAndDeletions.ts +7 -4
  51. package/src/preact/webWorkers/useWebWorker.ts +51 -0
  52. package/src/preact/webWorkers/workerFunction.ts +14 -0
  53. package/src/query/queryAggregatedDataOverTime.ts +3 -3
  54. package/src/query/queryMutationsOverTime.spec.ts +272 -51
  55. package/src/query/queryMutationsOverTime.ts +114 -47
  56. package/src/query/queryPrevalenceOverTime.ts +2 -2
  57. package/src/query/queryRelativeGrowthAdvantage.ts +3 -3
  58. package/src/types.ts +25 -5
  59. package/src/utils/map2d.spec.ts +79 -12
  60. package/src/utils/map2d.ts +25 -5
  61. package/src/utils/mutations.spec.ts +20 -20
  62. package/src/utils/mutations.ts +80 -17
  63. package/src/utils/sort.ts +5 -2
  64. package/src/utils/temporal.spec.ts +27 -24
  65. package/src/utils/{temporal.ts → temporalClass.ts} +170 -72
  66. package/src/utils/temporalTestHelpers.ts +3 -3
  67. package/src/web-components/input/gs-date-range-selector.stories.ts +16 -28
  68. package/src/web-components/input/gs-date-range-selector.tsx +17 -32
  69. package/src/web-components/introduction.mdx +46 -0
  70. package/src/web-components/visualization/gs-mutations-over-time.stories.ts +6 -699
  71. package/src/web-components/visualization/gs-mutations-over-time.tsx +2 -2
  72. package/standalone-bundle/dashboard-components.js +12011 -12778
  73. package/standalone-bundle/dashboard-components.js.map +1 -1
  74. package/src/preact/mutationsOverTime/__mockData__/aggregated_2024_01.json +0 -13
  75. package/src/preact/mutationsOverTime/__mockData__/aggregated_2024_02.json +0 -13
  76. package/src/preact/mutationsOverTime/__mockData__/aggregated_2024_03.json +0 -13
  77. package/src/preact/mutationsOverTime/__mockData__/aggregated_2024_04.json +0 -13
  78. package/src/preact/mutationsOverTime/__mockData__/aggregated_2024_05.json +0 -13
  79. package/src/preact/mutationsOverTime/__mockData__/aggregated_2024_06.json +0 -13
  80. package/src/preact/mutationsOverTime/__mockData__/aggregated_2024_07.json +0 -13
  81. package/src/preact/mutationsOverTime/__mockData__/aggregated_20_01_2024.json +0 -13
  82. package/src/preact/mutationsOverTime/__mockData__/aggregated_21_01_2024.json +0 -13
  83. package/src/preact/mutationsOverTime/__mockData__/aggregated_22_01_2024.json +0 -13
  84. package/src/preact/mutationsOverTime/__mockData__/aggregated_23_01_2024.json +0 -13
  85. package/src/preact/mutationsOverTime/__mockData__/aggregated_24_01_2024.json +0 -13
  86. package/src/preact/mutationsOverTime/__mockData__/aggregated_25_01_2024.json +0 -13
  87. package/src/preact/mutationsOverTime/__mockData__/aggregated_26_01_2024.json +0 -13
  88. package/src/preact/mutationsOverTime/__mockData__/aggregated_byDay.json +0 -38
  89. package/src/preact/mutationsOverTime/__mockData__/aggregated_byWeek.json +0 -122
  90. package/src/preact/mutationsOverTime/__mockData__/aggregated_date.json +0 -642
  91. package/src/preact/mutationsOverTime/__mockData__/aggregated_tooManyMutations.json +0 -1470
  92. package/src/preact/mutationsOverTime/__mockData__/aggregated_tooManyMutations_total.json +0 -13
  93. package/src/preact/mutationsOverTime/__mockData__/aggregated_week3_2024.json +0 -13
  94. package/src/preact/mutationsOverTime/__mockData__/aggregated_week4_2024.json +0 -13
  95. package/src/preact/mutationsOverTime/__mockData__/aggregated_week5_2024.json +0 -13
  96. package/src/preact/mutationsOverTime/__mockData__/aggregated_week6_2024.json +0 -13
  97. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutations_20_01_2024.json +0 -6778
  98. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutations_21_01_2024.json +0 -7129
  99. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutations_22_01_2024.json +0 -4681
  100. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutations_23_01_2024.json +0 -10738
  101. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutations_24_01_2024.json +0 -11710
  102. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutations_25_01_2024.json +0 -11557
  103. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutations_26_01_2024.json +0 -8596
  104. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutations_byDayOverall.json +0 -4726
  105. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_2024_01.json +0 -1747
  106. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_2024_02.json +0 -1774
  107. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_2024_03.json +0 -1819
  108. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_2024_04.json +0 -1864
  109. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_2024_05.json +0 -1927
  110. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_2024_06.json +0 -1864
  111. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_2024_07.json +0 -9
  112. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_byMonthOverall.json +0 -11143
  113. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_byWeekOverall.json +0 -9154
  114. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_tooManyMutations.json +0 -16453
  115. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_week3_2024.json +0 -8812
  116. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_week4_2024.json +0 -9730
  117. package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_week5_2024.json +0 -9865
  118. 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,92 @@ 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 { BaseMutationOverTimeDataMap } from '../preact/mutationsOverTime/MutationOverTimeData';
10
+ import { sortSubstitutionsAndDeletions } from '../preact/shared/sort/sortSubstitutionsAndDeletions';
9
11
  import {
12
+ type DeletionEntry,
10
13
  type LapisFilter,
11
14
  type SequenceType,
15
+ type SubstitutionEntry,
12
16
  type SubstitutionOrDeletionEntry,
13
17
  type TemporalGranularity,
14
18
  } from '../types';
15
- import { Map2dBase, type Map2d } from '../utils/map2d';
16
- import { type Deletion, type Substitution } from '../utils/mutations';
19
+ import { type Map2d } from '../utils/map2d';
17
20
  import {
21
+ type Deletion,
22
+ type DeletionClass,
23
+ type Substitution,
24
+ type SubstitutionClass,
25
+ toSubstitutionOrDeletion,
26
+ } from '../utils/mutations';
27
+ import {
28
+ compareTemporal,
18
29
  dateRangeCompare,
19
30
  generateAllInRange,
20
31
  getMinMaxTemporal,
21
32
  parseDateStringToTemporal,
22
33
  type Temporal,
23
- } from '../utils/temporal';
34
+ type TemporalClass,
35
+ toTemporal,
36
+ } from '../utils/temporalClass';
24
37
 
25
38
  export type MutationOverTimeData = {
26
- date: Temporal;
39
+ date: TemporalClass;
27
40
  mutations: SubstitutionOrDeletionEntry[];
28
41
  totalCount: number;
29
42
  };
30
43
 
31
44
  export type MutationOverTimeMutationValue = { proportion: number; count: number; totalCount: number };
32
45
  export type MutationOverTimeDataGroupedByMutation = Map2d<
33
- Substitution | Deletion,
34
- Temporal,
46
+ SubstitutionClass | DeletionClass,
47
+ TemporalClass,
35
48
  MutationOverTimeMutationValue
36
49
  >;
37
50
 
38
51
  const MAX_NUMBER_OF_GRID_COLUMNS = 200;
39
52
 
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);
53
+ export async function queryOverallMutationData({
54
+ lapisFilter,
55
+ sequenceType,
56
+ lapis,
57
+ granularity,
58
+ lapisDateField,
59
+ signal,
60
+ }: {
61
+ lapisFilter: LapisFilter;
62
+ sequenceType: SequenceType;
63
+ lapis: string;
64
+ granularity: TemporalGranularity;
65
+ lapisDateField: string;
66
+ signal?: AbortSignal;
67
+ }) {
68
+ const allDates = await getDatesInDataset(lapisFilter, lapis, granularity, lapisDateField, signal);
69
+ const filter = {
70
+ ...lapisFilter,
71
+ [`${lapisDateField}From`]: allDates[0].firstDay.toString(),
72
+ [`${lapisDateField}To`]: allDates[allDates.length - 1].lastDay.toString(),
73
+ };
74
+
75
+ return fetchAndPrepareSubstitutionsOrDeletions(filter, sequenceType).evaluate(lapis, signal);
47
76
  }
48
77
 
49
- export async function queryMutationsOverTimeData(
50
- lapisFilter: LapisFilter,
51
- sequenceType: SequenceType,
52
- lapis: string,
53
- lapisDateField: string,
54
- granularity: TemporalGranularity,
55
- signal?: AbortSignal,
56
- ) {
78
+ export type MutationOverTimeQuery = {
79
+ lapisFilter: LapisFilter;
80
+ sequenceType: SequenceType;
81
+ lapis: string;
82
+ lapisDateField: string;
83
+ granularity: TemporalGranularity;
84
+ signal?: AbortSignal;
85
+ };
86
+
87
+ export async function queryMutationsOverTimeData({
88
+ lapisFilter,
89
+ sequenceType,
90
+ lapis,
91
+ lapisDateField,
92
+ granularity,
93
+ signal,
94
+ }: MutationOverTimeQuery) {
57
95
  const allDates = await getDatesInDataset(lapisFilter, lapis, granularity, lapisDateField, signal);
58
96
 
59
97
  if (allDates.length > MAX_NUMBER_OF_GRID_COLUMNS) {
@@ -86,7 +124,20 @@ export async function queryMutationsOverTimeData(
86
124
 
87
125
  const data = await Promise.all(subQueries);
88
126
 
89
- return groupByMutation(data);
127
+ const overallMutationsData = (
128
+ await queryOverallMutationData({
129
+ lapisFilter,
130
+ sequenceType,
131
+ lapis,
132
+ lapisDateField,
133
+ granularity,
134
+ })
135
+ ).content;
136
+
137
+ return {
138
+ mutationOverTimeData: groupByMutation(data, overallMutationsData),
139
+ overallMutationData: overallMutationsData,
140
+ };
90
141
  }
91
142
 
92
143
  async function getDatesInDataset(
@@ -156,39 +207,55 @@ function fetchAndPrepareSubstitutionsOrDeletions(filter: LapisFilter, sequenceTy
156
207
  return new FetchSubstitutionsOrDeletionsOperator(filter, sequenceType, 0.001);
157
208
  }
158
209
 
159
- export function groupByMutation(data: MutationOverTimeData[]) {
160
- const dataArray = new Map2dBase<Substitution | Deletion, Temporal, MutationOverTimeMutationValue>(
161
- (mutation) => mutation.code,
162
- (date) => date.toString(),
163
- );
210
+ export function serializeSubstitutionOrDeletion(mutation: Substitution | Deletion) {
211
+ return mutation.code;
212
+ }
164
213
 
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,
214
+ export function serializeTemporal(date: Temporal) {
215
+ return date.dateString;
216
+ }
217
+
218
+ export function groupByMutation(
219
+ data: MutationOverTimeData[],
220
+ overallMutationData: (SubstitutionEntry | DeletionEntry)[],
221
+ ) {
222
+ const dataArray = new BaseMutationOverTimeDataMap();
223
+
224
+ const allDates = data.map((mutationData) => mutationData.date);
225
+
226
+ const sortedOverallMutationData = overallMutationData
227
+ .sort((a, b) => sortSubstitutionsAndDeletions(a.mutation, b.mutation))
228
+ .map((entry) => {
229
+ return toSubstitutionOrDeletion(entry.mutation);
230
+ });
231
+ const sortedDates = allDates.sort((a, b) => compareTemporal(a, b)).map((date) => toTemporal(date));
232
+
233
+ sortedOverallMutationData.forEach((mutationData) => {
234
+ sortedDates.forEach((date) => {
235
+ dataArray.set(mutationData, date, {
236
+ count: 0,
237
+ proportion: 0,
238
+ totalCount: 0,
171
239
  });
172
240
  });
173
241
  });
174
242
 
175
- addZeroValuesForDatesWithNoMutationData(dataArray, data);
176
-
177
- return dataArray;
178
- }
243
+ data.forEach((mutationData) => {
244
+ mutationData.mutations.forEach((mutationEntry) => {
245
+ const mutation = toSubstitutionOrDeletion(mutationEntry.mutation);
246
+ const date = toTemporal(mutationData.date);
179
247
 
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 });
248
+ if (dataArray.get(mutation, date) !== undefined) {
249
+ dataArray.set(mutation, date, {
250
+ count: mutationEntry.count,
251
+ proportion: mutationEntry.proportion,
252
+ totalCount: mutationData.totalCount,
253
+ });
189
254
  }
190
255
  });
191
- }
256
+ });
257
+
258
+ return dataArray;
192
259
  }
193
260
 
194
261
  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(