@genspectrum/dashboard-components 1.14.1 → 1.14.2

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/dist/util.d.ts CHANGED
@@ -1106,11 +1106,7 @@ declare global {
1106
1106
 
1107
1107
  declare global {
1108
1108
  interface HTMLElementTagNameMap {
1109
- 'gs-date-range-filter': DateRangeFilterComponent;
1110
- }
1111
- interface HTMLElementEventMap {
1112
- [gsEventNames.dateRangeFilterChanged]: CustomEvent<Record<string, string>>;
1113
- [gsEventNames.dateRangeOptionChanged]: DateRangeOptionChangedEvent;
1109
+ 'gs-genome-data-viewer': GenomeDataViewerComponent;
1114
1110
  }
1115
1111
  }
1116
1112
 
@@ -1118,7 +1114,7 @@ declare global {
1118
1114
  declare global {
1119
1115
  namespace JSX {
1120
1116
  interface IntrinsicElements {
1121
- 'gs-date-range-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1117
+ 'gs-genome-data-viewer': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1122
1118
  }
1123
1119
  }
1124
1120
  }
@@ -1126,10 +1122,7 @@ declare global {
1126
1122
 
1127
1123
  declare global {
1128
1124
  interface HTMLElementTagNameMap {
1129
- 'gs-location-filter': LocationFilterComponent;
1130
- }
1131
- interface HTMLElementEventMap {
1132
- [gsEventNames.locationChanged]: LocationChangedEvent;
1125
+ 'gs-mutation-comparison': MutationComparisonComponent;
1133
1126
  }
1134
1127
  }
1135
1128
 
@@ -1137,7 +1130,7 @@ declare global {
1137
1130
  declare global {
1138
1131
  namespace JSX {
1139
1132
  interface IntrinsicElements {
1140
- 'gs-location-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1133
+ 'gs-mutation-comparison': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1141
1134
  }
1142
1135
  }
1143
1136
  }
@@ -1145,10 +1138,7 @@ declare global {
1145
1138
 
1146
1139
  declare global {
1147
1140
  interface HTMLElementTagNameMap {
1148
- 'gs-text-filter': TextFilterComponent;
1149
- }
1150
- interface HTMLElementEventMap {
1151
- [gsEventNames.textFilterChanged]: TextFilterChangedEvent;
1141
+ 'gs-mutations': MutationsComponent;
1152
1142
  }
1153
1143
  }
1154
1144
 
@@ -1156,7 +1146,7 @@ declare global {
1156
1146
  declare global {
1157
1147
  namespace JSX {
1158
1148
  interface IntrinsicElements {
1159
- 'gs-text-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1149
+ 'gs-mutations': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1160
1150
  }
1161
1151
  }
1162
1152
  }
@@ -1164,10 +1154,7 @@ declare global {
1164
1154
 
1165
1155
  declare global {
1166
1156
  interface HTMLElementTagNameMap {
1167
- 'gs-mutation-filter': MutationFilterComponent;
1168
- }
1169
- interface HTMLElementEventMap {
1170
- [gsEventNames.mutationFilterChanged]: CustomEvent<MutationsFilter>;
1157
+ 'gs-relative-growth-advantage': RelativeGrowthAdvantageComponent;
1171
1158
  }
1172
1159
  }
1173
1160
 
@@ -1175,7 +1162,7 @@ declare global {
1175
1162
  declare global {
1176
1163
  namespace JSX {
1177
1164
  interface IntrinsicElements {
1178
- 'gs-mutation-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1165
+ 'gs-relative-growth-advantage': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1179
1166
  }
1180
1167
  }
1181
1168
  }
@@ -1183,11 +1170,7 @@ declare global {
1183
1170
 
1184
1171
  declare global {
1185
1172
  interface HTMLElementTagNameMap {
1186
- 'gs-lineage-filter': LineageFilterComponent;
1187
- }
1188
- interface HTMLElementEventMap {
1189
- [gsEventNames.lineageFilterChanged]: LineageFilterChangedEvent;
1190
- [gsEventNames.lineageFilterMultiChanged]: LineageMultiFilterChangedEvent;
1173
+ 'gs-prevalence-over-time': PrevalenceOverTimeComponent;
1191
1174
  }
1192
1175
  }
1193
1176
 
@@ -1195,7 +1178,7 @@ declare global {
1195
1178
  declare global {
1196
1179
  namespace JSX {
1197
1180
  interface IntrinsicElements {
1198
- 'gs-lineage-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1181
+ 'gs-prevalence-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1199
1182
  }
1200
1183
  }
1201
1184
  }
@@ -1203,11 +1186,7 @@ declare global {
1203
1186
 
1204
1187
  declare global {
1205
1188
  interface HTMLElementTagNameMap {
1206
- 'gs-number-range-filter': NumberRangeFilterComponent;
1207
- }
1208
- interface HTMLElementEventMap {
1209
- [gsEventNames.numberRangeFilterChanged]: NumberRangeFilterChangedEvent;
1210
- [gsEventNames.numberRangeValueChanged]: NumberRangeValueChangedEvent;
1189
+ 'gs-aggregate': AggregateComponent;
1211
1190
  }
1212
1191
  }
1213
1192
 
@@ -1215,7 +1194,7 @@ declare global {
1215
1194
  declare global {
1216
1195
  namespace JSX {
1217
1196
  interface IntrinsicElements {
1218
- 'gs-number-range-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1197
+ 'gs-aggregate': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1219
1198
  }
1220
1199
  }
1221
1200
  }
@@ -1223,7 +1202,7 @@ declare global {
1223
1202
 
1224
1203
  declare global {
1225
1204
  interface HTMLElementTagNameMap {
1226
- 'gs-genome-data-viewer': GenomeDataViewerComponent;
1205
+ 'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
1227
1206
  }
1228
1207
  }
1229
1208
 
@@ -1231,7 +1210,7 @@ declare global {
1231
1210
  declare global {
1232
1211
  namespace JSX {
1233
1212
  interface IntrinsicElements {
1234
- 'gs-genome-data-viewer': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1213
+ 'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1235
1214
  }
1236
1215
  }
1237
1216
  }
@@ -1239,7 +1218,7 @@ declare global {
1239
1218
 
1240
1219
  declare global {
1241
1220
  interface HTMLElementTagNameMap {
1242
- 'gs-mutation-comparison': MutationComparisonComponent;
1221
+ 'gs-mutations-over-time': MutationsOverTimeComponent;
1243
1222
  }
1244
1223
  }
1245
1224
 
@@ -1247,7 +1226,7 @@ declare global {
1247
1226
  declare global {
1248
1227
  namespace JSX {
1249
1228
  interface IntrinsicElements {
1250
- 'gs-mutation-comparison': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1229
+ 'gs-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1251
1230
  }
1252
1231
  }
1253
1232
  }
@@ -1255,7 +1234,7 @@ declare global {
1255
1234
 
1256
1235
  declare global {
1257
1236
  interface HTMLElementTagNameMap {
1258
- 'gs-mutations': MutationsComponent;
1237
+ 'gs-queries-over-time': QueriesOverTimeComponent;
1259
1238
  }
1260
1239
  }
1261
1240
 
@@ -1263,7 +1242,7 @@ declare global {
1263
1242
  declare global {
1264
1243
  namespace JSX {
1265
1244
  interface IntrinsicElements {
1266
- 'gs-mutations': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1245
+ 'gs-queries-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1267
1246
  }
1268
1247
  }
1269
1248
  }
@@ -1271,7 +1250,7 @@ declare global {
1271
1250
 
1272
1251
  declare global {
1273
1252
  interface HTMLElementTagNameMap {
1274
- 'gs-prevalence-over-time': PrevalenceOverTimeComponent;
1253
+ 'gs-sequences-by-location': SequencesByLocationComponent;
1275
1254
  }
1276
1255
  }
1277
1256
 
@@ -1279,7 +1258,7 @@ declare global {
1279
1258
  declare global {
1280
1259
  namespace JSX {
1281
1260
  interface IntrinsicElements {
1282
- 'gs-prevalence-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1261
+ 'gs-sequences-by-location': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1283
1262
  }
1284
1263
  }
1285
1264
  }
@@ -1287,7 +1266,7 @@ declare global {
1287
1266
 
1288
1267
  declare global {
1289
1268
  interface HTMLElementTagNameMap {
1290
- 'gs-relative-growth-advantage': RelativeGrowthAdvantageComponent;
1269
+ 'gs-statistics': StatisticsComponent;
1291
1270
  }
1292
1271
  }
1293
1272
 
@@ -1295,7 +1274,7 @@ declare global {
1295
1274
  declare global {
1296
1275
  namespace JSX {
1297
1276
  interface IntrinsicElements {
1298
- 'gs-relative-growth-advantage': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1277
+ 'gs-statistics': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1299
1278
  }
1300
1279
  }
1301
1280
  }
@@ -1303,7 +1282,11 @@ declare global {
1303
1282
 
1304
1283
  declare global {
1305
1284
  interface HTMLElementTagNameMap {
1306
- 'gs-aggregate': AggregateComponent;
1285
+ 'gs-date-range-filter': DateRangeFilterComponent;
1286
+ }
1287
+ interface HTMLElementEventMap {
1288
+ [gsEventNames.dateRangeFilterChanged]: CustomEvent<Record<string, string>>;
1289
+ [gsEventNames.dateRangeOptionChanged]: DateRangeOptionChangedEvent;
1307
1290
  }
1308
1291
  }
1309
1292
 
@@ -1311,7 +1294,7 @@ declare global {
1311
1294
  declare global {
1312
1295
  namespace JSX {
1313
1296
  interface IntrinsicElements {
1314
- 'gs-aggregate': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1297
+ 'gs-date-range-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1315
1298
  }
1316
1299
  }
1317
1300
  }
@@ -1319,7 +1302,10 @@ declare global {
1319
1302
 
1320
1303
  declare global {
1321
1304
  interface HTMLElementTagNameMap {
1322
- 'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
1305
+ 'gs-location-filter': LocationFilterComponent;
1306
+ }
1307
+ interface HTMLElementEventMap {
1308
+ [gsEventNames.locationChanged]: LocationChangedEvent;
1323
1309
  }
1324
1310
  }
1325
1311
 
@@ -1327,7 +1313,7 @@ declare global {
1327
1313
  declare global {
1328
1314
  namespace JSX {
1329
1315
  interface IntrinsicElements {
1330
- 'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1316
+ 'gs-location-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1331
1317
  }
1332
1318
  }
1333
1319
  }
@@ -1335,7 +1321,10 @@ declare global {
1335
1321
 
1336
1322
  declare global {
1337
1323
  interface HTMLElementTagNameMap {
1338
- 'gs-mutations-over-time': MutationsOverTimeComponent;
1324
+ 'gs-text-filter': TextFilterComponent;
1325
+ }
1326
+ interface HTMLElementEventMap {
1327
+ [gsEventNames.textFilterChanged]: TextFilterChangedEvent;
1339
1328
  }
1340
1329
  }
1341
1330
 
@@ -1343,7 +1332,7 @@ declare global {
1343
1332
  declare global {
1344
1333
  namespace JSX {
1345
1334
  interface IntrinsicElements {
1346
- 'gs-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1335
+ 'gs-text-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1347
1336
  }
1348
1337
  }
1349
1338
  }
@@ -1351,7 +1340,10 @@ declare global {
1351
1340
 
1352
1341
  declare global {
1353
1342
  interface HTMLElementTagNameMap {
1354
- 'gs-sequences-by-location': SequencesByLocationComponent;
1343
+ 'gs-mutation-filter': MutationFilterComponent;
1344
+ }
1345
+ interface HTMLElementEventMap {
1346
+ [gsEventNames.mutationFilterChanged]: CustomEvent<MutationsFilter>;
1355
1347
  }
1356
1348
  }
1357
1349
 
@@ -1359,7 +1351,7 @@ declare global {
1359
1351
  declare global {
1360
1352
  namespace JSX {
1361
1353
  interface IntrinsicElements {
1362
- 'gs-sequences-by-location': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1354
+ 'gs-mutation-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1363
1355
  }
1364
1356
  }
1365
1357
  }
@@ -1367,7 +1359,11 @@ declare global {
1367
1359
 
1368
1360
  declare global {
1369
1361
  interface HTMLElementTagNameMap {
1370
- 'gs-statistics': StatisticsComponent;
1362
+ 'gs-lineage-filter': LineageFilterComponent;
1363
+ }
1364
+ interface HTMLElementEventMap {
1365
+ [gsEventNames.lineageFilterChanged]: LineageFilterChangedEvent;
1366
+ [gsEventNames.lineageFilterMultiChanged]: LineageMultiFilterChangedEvent;
1371
1367
  }
1372
1368
  }
1373
1369
 
@@ -1375,7 +1371,7 @@ declare global {
1375
1371
  declare global {
1376
1372
  namespace JSX {
1377
1373
  interface IntrinsicElements {
1378
- 'gs-statistics': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1374
+ 'gs-lineage-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1379
1375
  }
1380
1376
  }
1381
1377
  }
@@ -1383,7 +1379,11 @@ declare global {
1383
1379
 
1384
1380
  declare global {
1385
1381
  interface HTMLElementTagNameMap {
1386
- 'gs-queries-over-time': QueriesOverTimeComponent;
1382
+ 'gs-number-range-filter': NumberRangeFilterComponent;
1383
+ }
1384
+ interface HTMLElementEventMap {
1385
+ [gsEventNames.numberRangeFilterChanged]: NumberRangeFilterChangedEvent;
1386
+ [gsEventNames.numberRangeValueChanged]: NumberRangeValueChangedEvent;
1387
1387
  }
1388
1388
  }
1389
1389
 
@@ -1391,7 +1391,7 @@ declare global {
1391
1391
  declare global {
1392
1392
  namespace JSX {
1393
1393
  interface IntrinsicElements {
1394
- 'gs-queries-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1394
+ 'gs-number-range-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1395
1395
  }
1396
1396
  }
1397
1397
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genspectrum/dashboard-components",
3
- "version": "1.14.1",
3
+ "version": "1.14.2",
4
4
  "description": "GenSpectrum web components for building dashboards",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-only",
@@ -126,7 +126,6 @@
126
126
  "depcheck": "^1.4.7",
127
127
  "eslint": "^9.31.0",
128
128
  "eslint-plugin-import": "^2.29.1",
129
- "eslint-plugin-jest": "^28.2.0",
130
129
  "eslint-plugin-react": "^7.37.5",
131
130
  "eslint-plugin-react-hooks": "^5.2.0",
132
131
  "eslint-plugin-storybook": "^0.12.0",
@@ -30,7 +30,7 @@ export type CustomColumn = z.infer<typeof customColumnSchema>;
30
30
  export interface FeatureRenderer<D> {
31
31
  asString(value: D): string;
32
32
  renderRowLabel(value: D): JSX.Element;
33
- renderTooltip(value: D, temporal: Temporal, proportionValue: ProportionValue | undefined): JSX.Element;
33
+ renderTooltip(value: D, temporal: Temporal, proportionValue: ProportionValue): JSX.Element;
34
34
  }
35
35
 
36
36
  export interface FeaturesOverTimeGridProps<F> {
@@ -99,12 +99,20 @@ function FeaturesOverTimeGrid<F>({
99
99
  </div>
100
100
  ),
101
101
  cell: ({ getValue, row, column, table }) => {
102
- const value = getValue();
102
+ const valueRaw = getValue();
103
+ const value = valueRaw ?? null;
103
104
  const rowIndex = row.index;
104
105
  const columnIndex = column.getIndex();
105
106
  const numberOfRows = table.getRowModel().rows.length;
106
107
  const numberOfColumns = table.getAllColumns().length;
107
108
 
109
+ if (valueRaw === undefined) {
110
+ // eslint-disable-next-line no-console -- We want to warn that something might be wrong.
111
+ console.error(
112
+ `Found undefined value for ${row.original.feature} - ${date.dateString}. This shouldn't happen.`,
113
+ );
114
+ }
115
+
108
116
  const tooltip = featureRenderer.renderTooltip(row.original.feature, date, value);
109
117
 
110
118
  return (
@@ -156,4 +156,59 @@ describe('groupMutationDataByLocation', () => {
156
156
 
157
157
  expect(location1Data.getFirstAxisKeys()).to.deep.equal([mutation1, mutation2, mutation3]);
158
158
  });
159
+
160
+ test('should backfill missing mutation-date combinations with explicit null', () => {
161
+ const input: WastewaterData = [
162
+ {
163
+ location: location1,
164
+ date: temporalCache.getYearMonthDay('2025-01-01'),
165
+ nucleotideMutationFrequency: [
166
+ { mutation: mutation1, proportion: 0.1 },
167
+ // mutation2 missing for this date
168
+ ],
169
+ aminoAcidMutationFrequency: [],
170
+ },
171
+ {
172
+ location: location1,
173
+ date: temporalCache.getYearMonthDay('2025-01-02'),
174
+ nucleotideMutationFrequency: [
175
+ // mutation1 missing for this date
176
+ { mutation: mutation2, proportion: 0.2 },
177
+ ],
178
+ aminoAcidMutationFrequency: [],
179
+ },
180
+ ];
181
+
182
+ const result = groupMutationDataByLocation(input, 'nucleotide');
183
+
184
+ expect(result).to.have.length(1);
185
+ const location1Data = result[0].data;
186
+
187
+ // Both mutations should appear in all dates
188
+ expect(location1Data.getFirstAxisKeys()).to.deep.equal([mutation1, mutation2]);
189
+ expect(location1Data.getSecondAxisKeys()).to.deep.equal([
190
+ temporalCache.getYearMonthDay('2025-01-01'),
191
+ temporalCache.getYearMonthDay('2025-01-02'),
192
+ ]);
193
+
194
+ // Verify backfilled nulls (not undefined)
195
+ expect(location1Data.get(mutation1, temporalCache.getYearMonthDay('2025-01-02'))).to.equal(null);
196
+ expect(location1Data.get(mutation2, temporalCache.getYearMonthDay('2025-01-01'))).to.equal(null);
197
+
198
+ // Verify actual values still present
199
+ expect(location1Data.get(mutation1, temporalCache.getYearMonthDay('2025-01-01'))).to.deep.equal({
200
+ type: 'wastewaterValue',
201
+ proportion: 0.1,
202
+ });
203
+ expect(location1Data.get(mutation2, temporalCache.getYearMonthDay('2025-01-02'))).to.deep.equal({
204
+ type: 'wastewaterValue',
205
+ proportion: 0.2,
206
+ });
207
+
208
+ // Verify the complete grid with getAsArray
209
+ expect(location1Data.getAsArray()).to.deep.equal([
210
+ [{ type: 'wastewaterValue', proportion: 0.1 }, null],
211
+ [null, { type: 'wastewaterValue', proportion: 0.2 }],
212
+ ]);
213
+ });
159
214
  });
@@ -21,15 +21,26 @@ export async function computeWastewaterMutationsOverTimeDataPerLocation(
21
21
 
22
22
  export function groupMutationDataByLocation(data: WastewaterData, sequenceType: 'nucleotide' | 'amino acid') {
23
23
  const locationMap = new Map<string, MutationOverTimeDataMap<TemporalClass>>();
24
+ // Track all unique mutations and dates per location for backfilling
25
+ const locationMutations = new Map<string, Set<string>>();
26
+ const locationDates = new Map<string, Set<string>>();
27
+
28
+ // First pass: populate sparse data and track all keys
24
29
  for (const row of data) {
25
30
  if (!locationMap.has(row.location)) {
26
31
  locationMap.set(row.location, new BaseMutationOverTimeDataMap<TemporalClass>());
32
+ locationMutations.set(row.location, new Set());
33
+ locationDates.set(row.location, new Set());
27
34
  }
28
35
  const map = locationMap.get(row.location)!;
36
+ const mutations = locationMutations.get(row.location)!;
37
+ const dates = locationDates.get(row.location)!;
29
38
 
30
39
  const mutationFrequencies =
31
40
  sequenceType === 'nucleotide' ? row.nucleotideMutationFrequency : row.aminoAcidMutationFrequency;
32
41
  for (const mutation of mutationFrequencies) {
42
+ dates.add(row.date.dateString);
43
+ mutations.add(mutation.mutation.code);
33
44
  map.set(
34
45
  mutation.mutation,
35
46
  row.date,
@@ -38,6 +49,21 @@ export function groupMutationDataByLocation(data: WastewaterData, sequenceType:
38
49
  }
39
50
  }
40
51
 
52
+ // Second pass: backfill missing cells with explicit null
53
+ for (const [location, map] of locationMap.entries()) {
54
+ const allMutations = Array.from(locationMutations.get(location)!);
55
+ const allDates = Array.from(locationDates.get(location)!).map((dateStr) => map.keysSecondAxis.get(dateStr)!);
56
+
57
+ for (const mutationCode of allMutations) {
58
+ const mutation = map.keysFirstAxis.get(mutationCode)!;
59
+ for (const date of allDates) {
60
+ if (map.get(mutation, date) === undefined) {
61
+ map.set(mutation, date, null);
62
+ }
63
+ }
64
+ }
65
+ }
66
+
41
67
  return [...locationMap.entries()].map(([location, data]) => ({
42
68
  location,
43
69
  data: new SortedMap2d(