@genspectrum/dashboard-components 1.2.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/custom-elements.json +20 -1
- package/dist/assets/mutationOverTimeWorker-CQxrFo53.js.map +1 -0
- package/dist/components.d.ts +34 -28
- package/dist/components.js +48 -16
- package/dist/components.js.map +1 -1
- package/dist/util.d.ts +28 -28
- package/package.json +1 -1
- package/src/lapisApi/lapisApi.ts +29 -0
- package/src/lapisApi/lapisTypes.ts +35 -1
- package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutationsByDay.ts +1 -0
- package/src/preact/mutationsOverTime/__mockData__/byWeek.ts +1 -0
- package/src/preact/mutationsOverTime/__mockData__/defaultMockData.ts +1 -0
- package/src/preact/mutationsOverTime/__mockData__/noDataWhenNoMutationsAreInFilter.ts +1 -0
- package/src/preact/mutationsOverTime/__mockData__/noDataWhenThereAreNoDatesInFilter.ts +1 -0
- package/src/preact/mutationsOverTime/__mockData__/showsMessageWhenTooManyMutations.ts +1 -0
- package/src/preact/mutationsOverTime/__mockData__/withDisplayMutations.ts +352 -0
- package/src/preact/mutationsOverTime/getFilteredMutationsOverTime.spec.ts +0 -24
- package/src/preact/mutationsOverTime/getFilteredMutationsOverTimeData.ts +0 -8
- package/src/preact/mutationsOverTime/mutationOverTimeWorker.mock.ts +2 -0
- package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +2 -0
- package/src/preact/mutationsOverTime/mutations-over-time.tsx +11 -6
- package/src/query/queryMutationsOverTime.spec.ts +98 -0
- package/src/query/queryMutationsOverTime.ts +172 -28
- package/src/query/queryMutationsOverTimeNewEndpoint.spec.ts +1057 -0
- package/src/web-components/visualization/gs-mutations-over-time.spec-d.ts +3 -0
- package/src/web-components/visualization/gs-mutations-over-time.stories.ts +3 -0
- package/src/web-components/visualization/gs-mutations-over-time.tsx +9 -0
- package/standalone-bundle/assets/mutationOverTimeWorker-CDACUs6w.js.map +1 -0
- package/standalone-bundle/dashboard-components.js +3628 -3594
- package/standalone-bundle/dashboard-components.js.map +1 -1
- package/dist/assets/mutationOverTimeWorker-DpW4YOGl.js.map +0 -1
- package/standalone-bundle/assets/mutationOverTimeWorker-CZVvQBze.js.map +0 -1
|
@@ -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,7 +17,14 @@ import {
|
|
|
16
17
|
type SubstitutionOrDeletionEntry,
|
|
17
18
|
type TemporalGranularity,
|
|
18
19
|
} from '../types';
|
|
19
|
-
import { type
|
|
20
|
+
import { type Map2DContents } from '../utils/map2d';
|
|
21
|
+
import {
|
|
22
|
+
type Deletion,
|
|
23
|
+
type Substitution,
|
|
24
|
+
toSubstitutionOrDeletion,
|
|
25
|
+
DeletionClass,
|
|
26
|
+
SubstitutionClass,
|
|
27
|
+
} from '../utils/mutations';
|
|
20
28
|
import {
|
|
21
29
|
compareTemporal,
|
|
22
30
|
dateRangeCompare,
|
|
@@ -54,12 +62,45 @@ export type MutationOverTimeMutationValue =
|
|
|
54
62
|
const MAX_NUMBER_OF_GRID_COLUMNS = 200;
|
|
55
63
|
export const MUTATIONS_OVER_TIME_MIN_PROPORTION = 0.001;
|
|
56
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Create SubstitutionOrDeletionEntry for given code with count and proportion 0.
|
|
67
|
+
* @param code a mutation code like G44T or A23-
|
|
68
|
+
*/
|
|
69
|
+
function codeToEmptyEntry(code: string): SubstitutionOrDeletionEntry | null {
|
|
70
|
+
const maybeDeletion = DeletionClass.parse(code);
|
|
71
|
+
if (maybeDeletion) {
|
|
72
|
+
return {
|
|
73
|
+
type: 'deletion',
|
|
74
|
+
mutation: maybeDeletion,
|
|
75
|
+
count: 0,
|
|
76
|
+
proportion: 0,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const maybeSubstitution = SubstitutionClass.parse(code);
|
|
80
|
+
if (maybeSubstitution) {
|
|
81
|
+
return {
|
|
82
|
+
type: 'substitution',
|
|
83
|
+
mutation: maybeSubstitution,
|
|
84
|
+
count: 0,
|
|
85
|
+
proportion: 0,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Return counts and proportions for all mutations that match the lapisFilter.
|
|
93
|
+
* If `includeMutations` are given, the result will also be filtered for those.
|
|
94
|
+
* Any mutation that isn't in the result, but is in the `includeMutations` will
|
|
95
|
+
* be in the result with count and proportion as 0.
|
|
96
|
+
*/
|
|
57
97
|
async function queryOverallMutationData({
|
|
58
98
|
lapisFilter,
|
|
59
99
|
sequenceType,
|
|
60
100
|
lapis,
|
|
61
101
|
granularity,
|
|
62
102
|
lapisDateField,
|
|
103
|
+
includeMutations,
|
|
63
104
|
signal,
|
|
64
105
|
}: {
|
|
65
106
|
lapisFilter: LapisFilter;
|
|
@@ -67,14 +108,23 @@ async function queryOverallMutationData({
|
|
|
67
108
|
lapis: string;
|
|
68
109
|
granularity: TemporalGranularity;
|
|
69
110
|
lapisDateField: string;
|
|
111
|
+
includeMutations?: string[];
|
|
70
112
|
signal?: AbortSignal;
|
|
71
113
|
}) {
|
|
72
114
|
const requestedDateRanges = await getDatesInDataset(lapisFilter, lapis, granularity, lapisDateField, signal);
|
|
73
115
|
|
|
74
116
|
if (requestedDateRanges.length === 0) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
117
|
+
if (includeMutations) {
|
|
118
|
+
return {
|
|
119
|
+
content: includeMutations
|
|
120
|
+
.map(codeToEmptyEntry)
|
|
121
|
+
.filter((e): e is SubstitutionOrDeletionEntry => e !== null),
|
|
122
|
+
};
|
|
123
|
+
} else {
|
|
124
|
+
return {
|
|
125
|
+
content: [],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
78
128
|
}
|
|
79
129
|
|
|
80
130
|
const filter = {
|
|
@@ -83,26 +133,39 @@ async function queryOverallMutationData({
|
|
|
83
133
|
[`${lapisDateField}To`]: requestedDateRanges[requestedDateRanges.length - 1].lastDay.toString(),
|
|
84
134
|
};
|
|
85
135
|
|
|
86
|
-
|
|
136
|
+
let dataPromise = fetchAndPrepareSubstitutionsOrDeletions(filter, sequenceType).evaluate(lapis, signal);
|
|
137
|
+
|
|
138
|
+
if (includeMutations) {
|
|
139
|
+
dataPromise = dataPromise.then((data) => {
|
|
140
|
+
return {
|
|
141
|
+
content: includeMutations
|
|
142
|
+
.map((code) => {
|
|
143
|
+
const found = data.content.find((m) => m.mutation.code === code);
|
|
144
|
+
return found ?? codeToEmptyEntry(code);
|
|
145
|
+
})
|
|
146
|
+
.filter((e): e is SubstitutionOrDeletionEntry => e !== null),
|
|
147
|
+
};
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return dataPromise;
|
|
87
152
|
}
|
|
88
153
|
|
|
89
154
|
export type MutationOverTimeQuery = {
|
|
90
155
|
lapisFilter: LapisFilter;
|
|
156
|
+
displayMutations?: string[];
|
|
91
157
|
sequenceType: SequenceType;
|
|
92
158
|
lapis: string;
|
|
93
159
|
lapisDateField: string;
|
|
94
160
|
granularity: TemporalGranularity;
|
|
161
|
+
useNewEndpoint?: boolean;
|
|
95
162
|
signal?: AbortSignal;
|
|
96
163
|
};
|
|
97
164
|
|
|
98
|
-
export async function queryMutationsOverTimeData({
|
|
99
|
-
lapisFilter,
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
lapisDateField,
|
|
103
|
-
granularity,
|
|
104
|
-
signal,
|
|
105
|
-
}: MutationOverTimeQuery) {
|
|
165
|
+
export async function queryMutationsOverTimeData(query: MutationOverTimeQuery) {
|
|
166
|
+
const { lapisFilter, displayMutations, sequenceType, lapis, lapisDateField, granularity, useNewEndpoint, signal } =
|
|
167
|
+
query;
|
|
168
|
+
|
|
106
169
|
const requestedDateRanges = await getDatesInDataset(lapisFilter, lapis, granularity, lapisDateField, signal);
|
|
107
170
|
|
|
108
171
|
if (requestedDateRanges.length > MAX_NUMBER_OF_GRID_COLUMNS) {
|
|
@@ -114,7 +177,26 @@ export async function queryMutationsOverTimeData({
|
|
|
114
177
|
);
|
|
115
178
|
}
|
|
116
179
|
|
|
117
|
-
const
|
|
180
|
+
const overallMutationData = queryOverallMutationData({
|
|
181
|
+
lapisFilter,
|
|
182
|
+
sequenceType,
|
|
183
|
+
lapis,
|
|
184
|
+
lapisDateField,
|
|
185
|
+
includeMutations: displayMutations,
|
|
186
|
+
granularity,
|
|
187
|
+
}).then((r) => r.content);
|
|
188
|
+
|
|
189
|
+
return useNewEndpoint === true
|
|
190
|
+
? queryMutationsOverTimeDataDirectEndpoint(requestedDateRanges, overallMutationData, query)
|
|
191
|
+
: queryMutationsOverTimeDataMultiQuery(requestedDateRanges, overallMutationData, query);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function queryMutationsOverTimeDataMultiQuery(
|
|
195
|
+
allDates: TemporalClass[],
|
|
196
|
+
overallMutationDataPromise: Promise<SubstitutionOrDeletionEntry[]>,
|
|
197
|
+
{ lapisFilter, sequenceType, lapis, lapisDateField, signal }: MutationOverTimeQuery,
|
|
198
|
+
) {
|
|
199
|
+
const subQueries = allDates.map(async (date) => {
|
|
118
200
|
const dateFrom = date.firstDay.toString();
|
|
119
201
|
const dateTo = date.lastDay.toString();
|
|
120
202
|
|
|
@@ -124,8 +206,10 @@ export async function queryMutationsOverTimeData({
|
|
|
124
206
|
[`${lapisDateField}To`]: dateTo,
|
|
125
207
|
};
|
|
126
208
|
|
|
127
|
-
const data = await
|
|
128
|
-
|
|
209
|
+
const [data, totalCountQuery] = await Promise.all([
|
|
210
|
+
fetchAndPrepareSubstitutionsOrDeletions(filter, sequenceType).evaluate(lapis, signal),
|
|
211
|
+
getTotalNumberOfSequencesInDateRange(filter).evaluate(lapis, signal),
|
|
212
|
+
]);
|
|
129
213
|
|
|
130
214
|
return {
|
|
131
215
|
date,
|
|
@@ -135,20 +219,80 @@ export async function queryMutationsOverTimeData({
|
|
|
135
219
|
});
|
|
136
220
|
|
|
137
221
|
const data = await Promise.all(subQueries);
|
|
222
|
+
const overallMutationData = await overallMutationDataPromise;
|
|
138
223
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
224
|
+
return {
|
|
225
|
+
mutationOverTimeData: groupByMutation(data, overallMutationData),
|
|
226
|
+
overallMutationData,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function queryMutationsOverTimeDataDirectEndpoint(
|
|
231
|
+
allDates: TemporalClass[],
|
|
232
|
+
overallMutationDataPromise: Promise<SubstitutionOrDeletionEntry[]>,
|
|
233
|
+
{ lapisFilter, sequenceType, lapis, lapisDateField, signal }: MutationOverTimeQuery,
|
|
234
|
+
): Promise<{
|
|
235
|
+
mutationOverTimeData: BaseMutationOverTimeDataMap;
|
|
236
|
+
overallMutationData: SubstitutionOrDeletionEntry[];
|
|
237
|
+
}> {
|
|
238
|
+
const overallMutationData = await overallMutationDataPromise;
|
|
239
|
+
overallMutationData.sort((a, b) => sortSubstitutionsAndDeletions(a.mutation, b.mutation));
|
|
240
|
+
const totalCounts = await Promise.all(
|
|
241
|
+
allDates.map(async (date) => {
|
|
242
|
+
const filter = {
|
|
243
|
+
...lapisFilter,
|
|
244
|
+
[`${lapisDateField}From`]: date.firstDay.toString(),
|
|
245
|
+
[`${lapisDateField}To`]: date.lastDay.toString(),
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const totalCountQuery = await getTotalNumberOfSequencesInDateRange(filter).evaluate(lapis, signal);
|
|
249
|
+
|
|
250
|
+
return totalCountQuery.content[0].count;
|
|
251
|
+
}),
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
const includeMutations = overallMutationData.map((value) => value.mutation.code);
|
|
255
|
+
const apiResult = await fetchMutationsOverTime(
|
|
256
|
+
lapis,
|
|
257
|
+
{
|
|
258
|
+
filters: lapisFilter,
|
|
259
|
+
dateRanges: allDates.map((date) => ({
|
|
260
|
+
dateFrom: date.firstDay.toString(),
|
|
261
|
+
dateTo: date.lastDay.toString(),
|
|
262
|
+
})),
|
|
263
|
+
includeMutations,
|
|
264
|
+
dateField: lapisDateField,
|
|
265
|
+
},
|
|
266
|
+
sequenceType,
|
|
267
|
+
signal,
|
|
268
|
+
);
|
|
269
|
+
|
|
270
|
+
const mutationOverTimeData: Map2DContents<Substitution | Deletion, Temporal, MutationOverTimeMutationValue> = {
|
|
271
|
+
keysFirstAxis: new Map(overallMutationData.map((value) => [value.mutation.code, value.mutation])),
|
|
272
|
+
keysSecondAxis: new Map(allDates.map((date) => [date.dateString, date])),
|
|
273
|
+
data: new Map(
|
|
274
|
+
overallMutationData.map((mutation, i) => [
|
|
275
|
+
mutation.mutation.code,
|
|
276
|
+
new Map(
|
|
277
|
+
allDates.map((date, j): [string, MutationOverTimeMutationValue] => [
|
|
278
|
+
date.dateString,
|
|
279
|
+
{
|
|
280
|
+
type: 'value',
|
|
281
|
+
// 'coverage' in the API resp. is the number of seqs. that have a non-ambiguous symbol at position
|
|
282
|
+
// 'count' in the API resp. is the number of seqs with the mutation
|
|
283
|
+
proportion: apiResult.data.data[i][j].count / apiResult.data.data[i][j].coverage,
|
|
284
|
+
count: apiResult.data.data[i][j].count,
|
|
285
|
+
totalCount: totalCounts[j],
|
|
286
|
+
},
|
|
287
|
+
]),
|
|
288
|
+
),
|
|
289
|
+
]),
|
|
290
|
+
),
|
|
291
|
+
};
|
|
148
292
|
|
|
149
293
|
return {
|
|
150
|
-
mutationOverTimeData:
|
|
151
|
-
overallMutationData
|
|
294
|
+
mutationOverTimeData: new BaseMutationOverTimeDataMap(mutationOverTimeData),
|
|
295
|
+
overallMutationData,
|
|
152
296
|
};
|
|
153
297
|
}
|
|
154
298
|
|
|
@@ -236,7 +380,7 @@ export function serializeTemporal(date: Temporal) {
|
|
|
236
380
|
export function groupByMutation(
|
|
237
381
|
data: MutationOverTimeData[],
|
|
238
382
|
overallMutationData: (SubstitutionEntry | DeletionEntry)[],
|
|
239
|
-
) {
|
|
383
|
+
): BaseMutationOverTimeDataMap {
|
|
240
384
|
const dataArray = new BaseMutationOverTimeDataMap();
|
|
241
385
|
|
|
242
386
|
const allDates = data.map((mutationData) => mutationData.date);
|