@genspectrum/dashboard-components 1.1.0 → 1.3.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 (109) hide show
  1. package/custom-elements.json +22 -3
  2. package/dist/{NumberRangeFilterChangedEvent-B64OQZjX.js → NumberRangeFilterChangedEvent-CQ32Qy8D.js} +2 -2
  3. package/dist/NumberRangeFilterChangedEvent-CQ32Qy8D.js.map +1 -0
  4. package/dist/assets/mutationOverTimeWorker-C7saVShx.js.map +1 -0
  5. package/dist/components.d.ts +33 -27
  6. package/dist/components.js +119 -127
  7. package/dist/components.js.map +1 -1
  8. package/dist/util.d.ts +27 -27
  9. package/dist/util.js +1 -1
  10. package/package.json +7 -3
  11. package/src/lapisApi/lapisApi.ts +31 -2
  12. package/src/lapisApi/lapisTypes.ts +35 -1
  13. package/src/operator/DivisionOperator.ts +4 -2
  14. package/src/operator/FetchDetailsOperator.ts +1 -1
  15. package/src/operator/RenameFieldOperator.ts +3 -3
  16. package/src/preact/aggregatedData/aggregate.tsx +0 -5
  17. package/src/preact/components/annotated-mutation.tsx +0 -1
  18. package/src/preact/components/clearable-select.stories.tsx +1 -1
  19. package/src/preact/components/confidence-interval-selector.tsx +1 -1
  20. package/src/preact/components/error-boundary.tsx +1 -5
  21. package/src/preact/components/error-display.tsx +1 -1
  22. package/src/preact/components/fullscreen.tsx +2 -5
  23. package/src/preact/components/info.stories.tsx +1 -1
  24. package/src/preact/components/min-max-range-slider.tsx +1 -1
  25. package/src/preact/components/proportion-selector.tsx +4 -4
  26. package/src/preact/components/select.tsx +1 -1
  27. package/src/preact/components/table.tsx +1 -1
  28. package/src/preact/components/tabs.tsx +1 -1
  29. package/src/preact/components/tooltip.stories.tsx +1 -1
  30. package/src/preact/components/tooltip.tsx +1 -1
  31. package/src/preact/genomeViewer/CDSPlot.tsx +3 -3
  32. package/src/preact/genomeViewer/loadGff3.ts +5 -8
  33. package/src/preact/lineageFilter/lineage-filter.tsx +1 -1
  34. package/src/preact/locationFilter/location-filter.tsx +4 -4
  35. package/src/preact/mutationComparison/getMutationComparisonTableData.ts +1 -3
  36. package/src/preact/mutationComparison/mutation-comparison-venn.tsx +1 -1
  37. package/src/preact/mutationComparison/mutation-comparison.tsx +0 -5
  38. package/src/preact/mutationFilter/mutation-filter-info.tsx +2 -2
  39. package/src/preact/mutationFilter/mutation-filter.tsx +1 -1
  40. package/src/preact/mutations/getMutationsGridData.ts +2 -6
  41. package/src/preact/mutations/getMutationsTableData.ts +1 -1
  42. package/src/preact/mutations/mutations-grid.tsx +1 -1
  43. package/src/preact/mutations/mutations.tsx +0 -5
  44. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutationsByDay.ts +1 -0
  45. package/src/preact/mutationsOverTime/__mockData__/byWeek.ts +1 -0
  46. package/src/preact/mutationsOverTime/__mockData__/defaultMockData.ts +1 -0
  47. package/src/preact/mutationsOverTime/__mockData__/noDataWhenNoMutationsAreInFilter.ts +1 -0
  48. package/src/preact/mutationsOverTime/__mockData__/noDataWhenThereAreNoDatesInFilter.ts +1 -0
  49. package/src/preact/mutationsOverTime/__mockData__/showsMessageWhenTooManyMutations.ts +1 -0
  50. package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +2 -0
  51. package/src/preact/mutationsOverTime/mutations-over-time.tsx +11 -11
  52. package/src/preact/numberRangeFilter/number-range-filter.tsx +4 -4
  53. package/src/preact/numberSequencesOverTime/getNumberOfSequencesOverTimeTableData.ts +1 -4
  54. package/src/preact/numberSequencesOverTime/number-sequences-over-time.tsx +0 -5
  55. package/src/preact/prevalenceOverTime/prevalence-over-time-bar-chart.tsx +1 -1
  56. package/src/preact/prevalenceOverTime/prevalence-over-time-bubble-chart.tsx +1 -1
  57. package/src/preact/prevalenceOverTime/prevalence-over-time-line-chart.tsx +1 -1
  58. package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +1 -1
  59. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage-chart.tsx +5 -5
  60. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +1 -4
  61. package/src/preact/sequencesByLocation/loadMapSource.tsx +5 -2
  62. package/src/preact/shared/aspectRatio/AspectRatio.tsx +1 -1
  63. package/src/preact/shared/floating-ui/hooks.ts +2 -2
  64. package/src/preact/shared/sort/sortMutationPositions.ts +2 -2
  65. package/src/preact/shared/tanstackTable/pagination.tsx +2 -2
  66. package/src/preact/shared/tanstackTable/tanstackTable.tsx +1 -1
  67. package/src/preact/statistic/statistics.tsx +0 -5
  68. package/src/preact/textFilter/fetchStringAutocompleteList.ts +1 -10
  69. package/src/preact/textFilter/text-filter.tsx +1 -6
  70. package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.tsx +1 -1
  71. package/src/preact/webWorkers/useWebWorker.ts +2 -1
  72. package/src/preact/webWorkers/workerFunction.ts +2 -2
  73. package/src/query/computeMapLocationData.ts +1 -1
  74. package/src/query/queryAggregatedDataOverTime.ts +3 -3
  75. package/src/query/queryMutationsOverTime.spec.ts +9 -9
  76. package/src/query/queryMutationsOverTime.ts +121 -37
  77. package/src/query/queryMutationsOverTimeNewEndpoint.spec.ts +935 -0
  78. package/src/query/queryRelativeGrowthAdvantage.ts +5 -9
  79. package/src/query/queryWastewaterMutationsOverTime.ts +1 -1
  80. package/src/types.ts +1 -1
  81. package/src/utils/mutations.ts +10 -10
  82. package/src/utils/type-utils.ts +1 -1
  83. package/src/utils/typeAssertions.spec.ts +1 -1
  84. package/src/web-components/gs-app.spec-d.ts +1 -1
  85. package/src/web-components/gs-app.stories.ts +1 -1
  86. package/src/web-components/input/gs-date-range-filter.tsx +2 -2
  87. package/src/web-components/input/gs-lineage-filter.tsx +2 -2
  88. package/src/web-components/input/gs-location-filter.tsx +3 -3
  89. package/src/web-components/input/gs-mutation-filter.tsx +2 -2
  90. package/src/web-components/input/gs-number-range-filter.spec.ts +1 -1
  91. package/src/web-components/input/gs-text-filter.tsx +2 -2
  92. package/src/web-components/visualization/gs-aggregate.tsx +2 -2
  93. package/src/web-components/visualization/gs-genome-data-viewer.spec-d.ts +1 -1
  94. package/src/web-components/visualization/gs-mutation-comparison.tsx +2 -2
  95. package/src/web-components/visualization/gs-mutations-over-time.spec-d.ts +3 -0
  96. package/src/web-components/visualization/gs-mutations-over-time.stories.ts +3 -0
  97. package/src/web-components/visualization/gs-mutations-over-time.tsx +9 -0
  98. package/src/web-components/visualization/gs-mutations.tsx +2 -2
  99. package/src/web-components/visualization/gs-number-sequences-over-time.tsx +2 -2
  100. package/src/web-components/visualization/gs-prevalence-over-time.tsx +2 -2
  101. package/src/web-components/visualization/gs-relative-growth-advantage.tsx +2 -2
  102. package/src/web-components/visualization/gs-sequences-by-location.tsx +2 -2
  103. package/src/web-components/visualization/gs-statistics.tsx +2 -2
  104. package/standalone-bundle/assets/mutationOverTimeWorker-DRRi3aMG.js.map +1 -0
  105. package/standalone-bundle/dashboard-components.js +4454 -4430
  106. package/standalone-bundle/dashboard-components.js.map +1 -1
  107. package/dist/NumberRangeFilterChangedEvent-B64OQZjX.js.map +0 -1
  108. package/dist/assets/mutationOverTimeWorker-DjH04AQB.js.map +0 -1
  109. package/standalone-bundle/assets/mutationOverTimeWorker-B6bf3R3j.js.map +0 -1
@@ -8,7 +8,6 @@ import { lapisFilterSchema } from '../../types';
8
8
  import { DownshiftCombobox } from '../components/downshift-combobox';
9
9
  import { ErrorBoundary } from '../components/error-boundary';
10
10
  import { LoadingDisplay } from '../components/loading-display';
11
- import { NoDataDisplay } from '../components/no-data-display';
12
11
  import { ResizeContainer } from '../components/resize-container';
13
12
  import { useQuery } from '../useQuery';
14
13
 
@@ -60,10 +59,6 @@ const TextFilterInner: FunctionComponent<TextFilterInnerProps> = ({
60
59
  throw error;
61
60
  }
62
61
 
63
- if (data === null) {
64
- return <NoDataDisplay />;
65
- }
66
-
67
62
  return <TextSelector lapisField={lapisField} value={value} placeholderText={placeholderText} data={data} />;
68
63
  };
69
64
 
@@ -106,5 +101,5 @@ function filterByInputValue(item: SelectItem, inputValue: string | null) {
106
101
  if (inputValue === null || inputValue === '') {
107
102
  return true;
108
103
  }
109
- return item.value?.toLowerCase().includes(inputValue?.toLowerCase() || '');
104
+ return item.value.toLowerCase().includes(inputValue.toLowerCase());
110
105
  }
@@ -100,7 +100,7 @@ function useDisplayedSegments(mutations: MutationOverTimeDataPerLocation) {
100
100
  const displayedSegments = useMemo(() => {
101
101
  const unique = [
102
102
  ...new Set(
103
- mutations.flatMap(({ data }) => data.getFirstAxisKeys().map((mutation) => mutation.segment || '')),
103
+ mutations.flatMap(({ data }) => data.getFirstAxisKeys().map((mutation) => mutation.segment ?? '')),
104
104
  ),
105
105
  ];
106
106
 
@@ -23,7 +23,8 @@ export type ErrorWorkerStatus =
23
23
  };
24
24
  export type WorkerStatus<Response> = LoadingWorkerStatus | SuccessWorkerStatus<Response> | ErrorWorkerStatus;
25
25
 
26
- export function useWebWorker<Request, Response>(messageToWorker: Request, WorkerConstructor: new () => Worker) {
26
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- We need the return type
27
+ export function useWebWorker<Response>(messageToWorker: unknown, WorkerConstructor: new () => Worker) {
27
28
  const [data, setData] = useState<Response | undefined>(undefined);
28
29
  const [error, setError] = useState<Error | undefined>(undefined);
29
30
  const [isLoading, setIsLoading] = useState(true);
@@ -1,7 +1,7 @@
1
1
  import { type ErrorWorkerStatus, type LoadingWorkerStatus, type SuccessWorkerStatus } from './useWebWorker';
2
2
  import { UserFacingError } from '../components/error-display';
3
3
 
4
- export async function workerFunction<R>(queryFunction: () => R) {
4
+ export async function workerFunction(queryFunction: () => unknown) {
5
5
  try {
6
6
  postMessage({ status: 'loading' } satisfies LoadingWorkerStatus);
7
7
 
@@ -10,7 +10,7 @@ export async function workerFunction<R>(queryFunction: () => R) {
10
10
  postMessage({
11
11
  status: 'success',
12
12
  data: workerResponse,
13
- } satisfies SuccessWorkerStatus<R>);
13
+ } satisfies SuccessWorkerStatus<unknown>);
14
14
  } catch (error) {
15
15
  postMessage(
16
16
  (error instanceof UserFacingError
@@ -83,7 +83,7 @@ function matchLocationDataAndGeoJsonFeatures(
83
83
 
84
84
  const locations: Feature<GeometryObject, EnhancedGeoJsonFeatureProperties>[] = geojsonData.features.map(
85
85
  (feature) => {
86
- const name = feature?.properties?.name;
86
+ const name = feature.properties.name;
87
87
  if (typeof name !== 'string') {
88
88
  throw new Error(
89
89
  `GeoJSON feature with id '${feature.id}' does not have 'properties.name' of type string, was: '${name}'`,
@@ -14,13 +14,13 @@ import {
14
14
  type TemporalClass,
15
15
  } from '../utils/temporalClass';
16
16
 
17
- export function queryAggregatedDataOverTime<LapisDateField extends string>(
17
+ export function queryAggregatedDataOverTime(
18
18
  filter: LapisFilter,
19
19
  granularity: TemporalGranularity,
20
20
  smoothingWindow: number,
21
- lapisDateField: LapisDateField,
21
+ lapisDateField: string,
22
22
  ) {
23
- const fetchData = new FetchAggregatedOperator<{ [key in LapisDateField]: string | null }>(filter, [lapisDateField]);
23
+ const fetchData = new FetchAggregatedOperator<Record<string, string | null>>(filter, [lapisDateField]);
24
24
  const dataWithFixedDateKey = new RenameFieldOperator(fetchData, lapisDateField, 'date');
25
25
  const mapData = new MapOperator(dataWithFixedDateKey, (d) => mapDateToGranularityRange(d, granularity));
26
26
  const groupByData = new GroupByAndSumOperator(mapData, 'dateRange', 'count');
@@ -231,18 +231,14 @@ describe('queryMutationsOverTime', () => {
231
231
  minProportion: 0.001,
232
232
  },
233
233
  response: {
234
- data: [
235
- getSomeTestMutation(0.1, 1),
236
- getSomeTestMutation(0.3, 3),
237
- getSomeOtherTestMutation(0.4, 4),
238
- ],
234
+ data: [getSomeTestMutation(0.2, 4), getSomeOtherTestMutation(0.4, 4)],
239
235
  },
240
236
  },
241
237
  ],
242
238
  'nucleotide',
243
239
  );
244
240
 
245
- const { mutationOverTimeData } = await queryMutationsOverTimeData({
241
+ const { mutationOverTimeData, overallMutationData } = await queryMutationsOverTimeData({
246
242
  lapisFilter,
247
243
  sequenceType: 'nucleotide',
248
244
  lapis: DUMMY_LAPIS_URL,
@@ -263,6 +259,8 @@ describe('queryMutationsOverTime', () => {
263
259
  ],
264
260
  ]);
265
261
 
262
+ expect(overallMutationData.length).toBe(2);
263
+
266
264
  const sequences = mutationOverTimeData.getFirstAxisKeys();
267
265
  expect(sequences[0].code).toBe('otherSequenceName:G234C');
268
266
  expect(sequences[1].code).toBe('sequenceName:A123T');
@@ -435,14 +433,14 @@ describe('queryMutationsOverTime', () => {
435
433
  minProportion: 0.001,
436
434
  },
437
435
  response: {
438
- data: [getSomeTestMutation(0.2, 2), getSomeTestMutation(0.3, 3)],
436
+ data: [getSomeTestMutation(0.25, 5)],
439
437
  },
440
438
  },
441
439
  ],
442
440
  'nucleotide',
443
441
  );
444
442
 
445
- const { mutationOverTimeData } = await queryMutationsOverTimeData({
443
+ const { mutationOverTimeData, overallMutationData } = await queryMutationsOverTimeData({
446
444
  lapisFilter,
447
445
  sequenceType: 'nucleotide',
448
446
  lapis: DUMMY_LAPIS_URL,
@@ -450,6 +448,8 @@ describe('queryMutationsOverTime', () => {
450
448
  granularity: 'day',
451
449
  });
452
450
 
451
+ expect(overallMutationData.length).toBe(1);
452
+
453
453
  expect(mutationOverTimeData.getAsArray()).to.deep.equal([
454
454
  [
455
455
  { type: 'value', proportion: 0.2, count: 2, totalCount: 11 },
@@ -527,7 +527,7 @@ describe('queryMutationsOverTime', () => {
527
527
  minProportion: 0.001,
528
528
  },
529
529
  response: {
530
- data: [getSomeTestMutation(0.1, 1), getSomeTestMutation(0.2, 2)],
530
+ data: [getSomeTestMutation(0.15, 3)],
531
531
  },
532
532
  },
533
533
  ],
@@ -1,4 +1,5 @@
1
1
  import { mapDateToGranularityRange } from './queryAggregatedDataOverTime';
2
+ import { fetchMutationsOverTime } from '../lapisApi/lapisApi';
2
3
  import { FetchAggregatedOperator } from '../operator/FetchAggregatedOperator';
3
4
  import { FetchSubstitutionsOrDeletionsOperator } from '../operator/FetchSubstitutionsOrDeletionsOperator';
4
5
  import { GroupByAndSumOperator } from '../operator/GroupByAndSumOperator';
@@ -16,6 +17,7 @@ import {
16
17
  type SubstitutionOrDeletionEntry,
17
18
  type TemporalGranularity,
18
19
  } from '../types';
20
+ import { type Map2DContents } from '../utils/map2d';
19
21
  import { type Deletion, type Substitution, toSubstitutionOrDeletion } from '../utils/mutations';
20
22
  import {
21
23
  compareTemporal,
@@ -54,7 +56,7 @@ export type MutationOverTimeMutationValue =
54
56
  const MAX_NUMBER_OF_GRID_COLUMNS = 200;
55
57
  export const MUTATIONS_OVER_TIME_MIN_PROPORTION = 0.001;
56
58
 
57
- export async function queryOverallMutationData({
59
+ async function queryOverallMutationData({
58
60
  lapisFilter,
59
61
  sequenceType,
60
62
  lapis,
@@ -69,9 +71,9 @@ export async function queryOverallMutationData({
69
71
  lapisDateField: string;
70
72
  signal?: AbortSignal;
71
73
  }) {
72
- const allDates = await getDatesInDataset(lapisFilter, lapis, granularity, lapisDateField, signal);
74
+ const requestedDateRanges = await getDatesInDataset(lapisFilter, lapis, granularity, lapisDateField, signal);
73
75
 
74
- if (allDates.length === 0) {
76
+ if (requestedDateRanges.length === 0) {
75
77
  return {
76
78
  content: [],
77
79
  };
@@ -79,8 +81,8 @@ export async function queryOverallMutationData({
79
81
 
80
82
  const filter = {
81
83
  ...lapisFilter,
82
- [`${lapisDateField}From`]: allDates[0].firstDay.toString(),
83
- [`${lapisDateField}To`]: allDates[allDates.length - 1].lastDay.toString(),
84
+ [`${lapisDateField}From`]: requestedDateRanges[0].firstDay.toString(),
85
+ [`${lapisDateField}To`]: requestedDateRanges[requestedDateRanges.length - 1].lastDay.toString(),
84
86
  };
85
87
 
86
88
  return fetchAndPrepareSubstitutionsOrDeletions(filter, sequenceType).evaluate(lapis, signal);
@@ -92,28 +94,42 @@ export type MutationOverTimeQuery = {
92
94
  lapis: string;
93
95
  lapisDateField: string;
94
96
  granularity: TemporalGranularity;
97
+ useNewEndpoint?: boolean;
95
98
  signal?: AbortSignal;
96
99
  };
97
100
 
98
- export async function queryMutationsOverTimeData({
99
- lapisFilter,
100
- sequenceType,
101
- lapis,
102
- lapisDateField,
103
- granularity,
104
- signal,
105
- }: MutationOverTimeQuery) {
106
- const allDates = await getDatesInDataset(lapisFilter, lapis, granularity, lapisDateField, signal);
101
+ export async function queryMutationsOverTimeData(query: MutationOverTimeQuery) {
102
+ const { lapisFilter, sequenceType, lapis, lapisDateField, granularity, useNewEndpoint, signal } = query;
103
+
104
+ const requestedDateRanges = await getDatesInDataset(lapisFilter, lapis, granularity, lapisDateField, signal);
107
105
 
108
- if (allDates.length > MAX_NUMBER_OF_GRID_COLUMNS) {
106
+ if (requestedDateRanges.length > MAX_NUMBER_OF_GRID_COLUMNS) {
109
107
  throw new UserFacingError(
110
108
  'Too many dates',
111
- `The dataset would contain ${allDates.length} date intervals. ` +
109
+ `The dataset would contain ${requestedDateRanges.length} date intervals. ` +
112
110
  `Please reduce the number to below ${MAX_NUMBER_OF_GRID_COLUMNS} to display the data. ` +
113
111
  'You can achieve this by either narrowing the date range in the provided LAPIS filter or by selecting a larger granularity.',
114
112
  );
115
113
  }
116
114
 
115
+ const overallMutationData = queryOverallMutationData({
116
+ lapisFilter,
117
+ sequenceType,
118
+ lapis,
119
+ lapisDateField,
120
+ granularity,
121
+ }).then((r) => r.content);
122
+
123
+ return useNewEndpoint === true
124
+ ? queryMutationsOverTimeDataDirectEndpoint(requestedDateRanges, overallMutationData, query)
125
+ : queryMutationsOverTimeDataMultiQuery(requestedDateRanges, overallMutationData, query);
126
+ }
127
+
128
+ async function queryMutationsOverTimeDataMultiQuery(
129
+ allDates: TemporalClass[],
130
+ overallMutationDataPromise: Promise<SubstitutionOrDeletionEntry[]>,
131
+ { lapisFilter, sequenceType, lapis, lapisDateField, signal }: MutationOverTimeQuery,
132
+ ) {
117
133
  const subQueries = allDates.map(async (date) => {
118
134
  const dateFrom = date.firstDay.toString();
119
135
  const dateTo = date.lastDay.toString();
@@ -124,8 +140,10 @@ export async function queryMutationsOverTimeData({
124
140
  [`${lapisDateField}To`]: dateTo,
125
141
  };
126
142
 
127
- const data = await fetchAndPrepareSubstitutionsOrDeletions(filter, sequenceType).evaluate(lapis, signal);
128
- const totalCountQuery = await getTotalNumberOfSequencesInDateRange(filter).evaluate(lapis, signal);
143
+ const [data, totalCountQuery] = await Promise.all([
144
+ fetchAndPrepareSubstitutionsOrDeletions(filter, sequenceType).evaluate(lapis, signal),
145
+ getTotalNumberOfSequencesInDateRange(filter).evaluate(lapis, signal),
146
+ ]);
129
147
 
130
148
  return {
131
149
  date,
@@ -135,23 +153,89 @@ export async function queryMutationsOverTimeData({
135
153
  });
136
154
 
137
155
  const data = await Promise.all(subQueries);
156
+ const overallMutationData = await overallMutationDataPromise;
138
157
 
139
- const overallMutationsData = (
140
- await queryOverallMutationData({
141
- lapisFilter,
142
- sequenceType,
143
- lapis,
144
- lapisDateField,
145
- granularity,
146
- })
147
- ).content;
158
+ return {
159
+ mutationOverTimeData: groupByMutation(data, overallMutationData),
160
+ overallMutationData,
161
+ };
162
+ }
163
+
164
+ async function queryMutationsOverTimeDataDirectEndpoint(
165
+ allDates: TemporalClass[],
166
+ overallMutationDataPromise: Promise<SubstitutionOrDeletionEntry[]>,
167
+ { lapisFilter, sequenceType, lapis, lapisDateField, signal }: MutationOverTimeQuery,
168
+ ): Promise<{
169
+ mutationOverTimeData: BaseMutationOverTimeDataMap;
170
+ overallMutationData: SubstitutionOrDeletionEntry[];
171
+ }> {
172
+ const overallMutationData = await overallMutationDataPromise;
173
+ overallMutationData.sort((a, b) => sortSubstitutionsAndDeletions(a.mutation, b.mutation));
174
+ const totalCounts = await Promise.all(
175
+ allDates.map(async (date) => {
176
+ const filter = {
177
+ ...lapisFilter,
178
+ [`${lapisDateField}From`]: date.firstDay.toString(),
179
+ [`${lapisDateField}To`]: date.lastDay.toString(),
180
+ };
181
+
182
+ const totalCountQuery = await getTotalNumberOfSequencesInDateRange(filter).evaluate(lapis, signal);
183
+
184
+ return totalCountQuery.content[0].count;
185
+ }),
186
+ );
187
+
188
+ const includeMutations = overallMutationData.map((value) => value.mutation.code);
189
+ const apiResult = await fetchMutationsOverTime(
190
+ lapis,
191
+ {
192
+ filters: lapisFilter,
193
+ dateRanges: allDates.map((date) => ({
194
+ dateFrom: date.firstDay.toString(),
195
+ dateTo: date.lastDay.toString(),
196
+ })),
197
+ includeMutations,
198
+ dateField: lapisDateField,
199
+ },
200
+ sequenceType,
201
+ signal,
202
+ );
203
+
204
+ const mutationOverTimeData: Map2DContents<Substitution | Deletion, Temporal, MutationOverTimeMutationValue> = {
205
+ keysFirstAxis: new Map(overallMutationData.map((value) => [value.mutation.code, value.mutation])),
206
+ keysSecondAxis: new Map(allDates.map((date) => [date.dateString, date])),
207
+ data: new Map(
208
+ overallMutationData.map((mutation, i) => [
209
+ mutation.mutation.code,
210
+ new Map(
211
+ allDates.map((date, j): [string, MutationOverTimeMutationValue] => [
212
+ date.dateString,
213
+ {
214
+ type: 'value',
215
+ // 'coverage' in the API resp. is the number of seqs. that have a non-ambiguous symbol at position
216
+ // 'count' in the API resp. is the number of seqs with the mutation
217
+ proportion: apiResult.data.data[i][j].count / apiResult.data.data[i][j].coverage,
218
+ count: apiResult.data.data[i][j].count,
219
+ totalCount: totalCounts[j],
220
+ },
221
+ ]),
222
+ ),
223
+ ]),
224
+ ),
225
+ };
148
226
 
149
227
  return {
150
- mutationOverTimeData: groupByMutation(data, overallMutationsData),
151
- overallMutationData: overallMutationsData,
228
+ mutationOverTimeData: new BaseMutationOverTimeDataMap(mutationOverTimeData),
229
+ overallMutationData,
152
230
  };
153
231
  }
154
232
 
233
+ /**
234
+ * Returns a list of date ranges as TemporalClass.
235
+ * Respects date range filters given in the lapisFilter as <lapisDateField>From and <lapisDateField>To.
236
+ * If either side (or both sides) of the range are not given, the min and max are determined from
237
+ * the available data.
238
+ */
155
239
  async function getDatesInDataset(
156
240
  lapisFilter: LapisFilter,
157
241
  lapis: string,
@@ -159,6 +243,11 @@ async function getDatesInDataset(
159
243
  lapisDateField: string,
160
244
  signal: AbortSignal | undefined,
161
245
  ) {
246
+ const { dateFrom, dateTo } = getDateRangeFromFilter(lapisFilter, lapisDateField, granularity);
247
+ if (dateFrom !== null && dateTo !== null) {
248
+ return generateAllInRange(dateFrom, dateTo);
249
+ }
250
+
162
251
  const { content: availableDates } = await queryAvailableDates(
163
252
  lapisFilter,
164
253
  lapis,
@@ -167,7 +256,6 @@ async function getDatesInDataset(
167
256
  signal,
168
257
  );
169
258
 
170
- const { dateFrom, dateTo } = getDateRangeFromFilter(lapisFilter, lapisDateField, granularity);
171
259
  const { min, max } = getMinMaxTemporal(availableDates);
172
260
 
173
261
  return generateAllInRange(dateFrom ?? min, dateTo ?? max);
@@ -202,12 +290,8 @@ function queryAvailableDates(
202
290
  return fetchAndPrepareDates(lapisFilter, granularity, lapisDateField).evaluate(lapis, signal);
203
291
  }
204
292
 
205
- function fetchAndPrepareDates<LapisDateField extends string>(
206
- filter: LapisFilter,
207
- granularity: TemporalGranularity,
208
- lapisDateField: LapisDateField,
209
- ) {
210
- const fetchData = new FetchAggregatedOperator<{ [key in LapisDateField]: string | null }>(filter, [lapisDateField]);
293
+ function fetchAndPrepareDates(filter: LapisFilter, granularity: TemporalGranularity, lapisDateField: string) {
294
+ const fetchData = new FetchAggregatedOperator<Record<string, string | null>>(filter, [lapisDateField]);
211
295
  const dataWithFixedDateKey = new RenameFieldOperator(fetchData, lapisDateField, 'date');
212
296
  const mapData = new MapOperator(dataWithFixedDateKey, (data) => mapDateToGranularityRange(data, granularity));
213
297
  const groupByData = new GroupByAndSumOperator(mapData, 'dateRange', 'count');
@@ -230,7 +314,7 @@ export function serializeTemporal(date: Temporal) {
230
314
  export function groupByMutation(
231
315
  data: MutationOverTimeData[],
232
316
  overallMutationData: (SubstitutionEntry | DeletionEntry)[],
233
- ) {
317
+ ): BaseMutationOverTimeDataMap {
234
318
  const dataArray = new BaseMutationOverTimeDataMap();
235
319
 
236
320
  const allDates = data.map((mutationData) => mutationData.date);