@genspectrum/dashboard-components 1.4.0 → 1.5.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 (34) hide show
  1. package/custom-elements.json +97 -5
  2. package/dist/assets/{mutationOverTimeWorker-CQxrFo53.js.map → mutationOverTimeWorker-BJ_P2T8Y.js.map} +1 -1
  3. package/dist/components.d.ts +45 -25
  4. package/dist/components.js +117 -20
  5. package/dist/components.js.map +1 -1
  6. package/dist/util.d.ts +25 -25
  7. package/package.json +1 -1
  8. package/src/preact/lineageFilter/lineage-filter.stories.tsx +24 -0
  9. package/src/preact/lineageFilter/lineage-filter.tsx +13 -2
  10. package/src/preact/locationFilter/location-filter.stories.tsx +24 -0
  11. package/src/preact/locationFilter/location-filter.tsx +19 -3
  12. package/src/preact/mutationsOverTime/__mockData__/withGaps.ts +352 -0
  13. package/src/preact/mutationsOverTime/getFilteredMutationsOverTime.spec.ts +38 -0
  14. package/src/preact/mutationsOverTime/getFilteredMutationsOverTimeData.ts +10 -0
  15. package/src/preact/mutationsOverTime/mutationOverTimeWorker.mock.ts +2 -0
  16. package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +35 -0
  17. package/src/preact/mutationsOverTime/mutations-over-time.tsx +28 -4
  18. package/src/preact/textFilter/text-filter.stories.tsx +29 -4
  19. package/src/preact/textFilter/text-filter.tsx +13 -2
  20. package/src/query/queryMutationsOverTime.ts +37 -4
  21. package/src/query/queryMutationsOverTimeNewEndpoint.spec.ts +122 -0
  22. package/src/utils/map2d.spec.ts +30 -0
  23. package/src/utils/map2d.ts +14 -1
  24. package/src/web-components/input/gs-lineage-filter.stories.ts +7 -0
  25. package/src/web-components/input/gs-lineage-filter.tsx +8 -0
  26. package/src/web-components/input/gs-location-filter.stories.ts +7 -0
  27. package/src/web-components/input/gs-location-filter.tsx +9 -1
  28. package/src/web-components/input/gs-text-filter.stories.ts +7 -0
  29. package/src/web-components/input/gs-text-filter.tsx +8 -0
  30. package/src/web-components/visualization/gs-mutations-over-time.stories.ts +14 -1
  31. package/src/web-components/visualization/gs-mutations-over-time.tsx +8 -0
  32. package/standalone-bundle/assets/{mutationOverTimeWorker-CDACUs6w.js.map → mutationOverTimeWorker-CkeGpKWp.js.map} +1 -1
  33. package/standalone-bundle/dashboard-components.js +1412 -1329
  34. package/standalone-bundle/dashboard-components.js.map +1 -1
@@ -0,0 +1,352 @@
1
+ import { type MutationOverTimeMockData } from './mockConversion';
2
+
3
+ export const withGaps: MutationOverTimeMockData = {
4
+ query: {
5
+ lapisFilter: {
6
+ pangoLineage: 'JN.1*',
7
+ dateFrom: '2024-01-15',
8
+ dateTo: '2024-07-10',
9
+ },
10
+ sequenceType: 'nucleotide',
11
+ granularity: 'month',
12
+ lapisDateField: 'date',
13
+ lapis: 'https://lapis.cov-spectrum.org/open/v2',
14
+ displayMutations: ['A19722G', 'G21641T', 'T21652-'],
15
+ useNewEndpoint: false,
16
+ },
17
+ response: {
18
+ overallMutationData: [
19
+ {
20
+ type: 'substitution',
21
+ mutation: {
22
+ valueAtReference: 'A',
23
+ substitutionValue: 'G',
24
+ position: 19722,
25
+ type: 'substitution',
26
+ code: 'A19722G',
27
+ },
28
+ count: 10234,
29
+ proportion: 0.09900453714363107,
30
+ },
31
+ {
32
+ type: 'substitution',
33
+ mutation: {
34
+ valueAtReference: 'G',
35
+ substitutionValue: 'T',
36
+ position: 21641,
37
+ type: 'substitution',
38
+ code: 'G21641T',
39
+ },
40
+ count: 4485,
41
+ proportion: 0.05001951709139575,
42
+ },
43
+ {
44
+ type: 'deletion',
45
+ mutation: {
46
+ valueAtReference: 'T',
47
+ position: 21652,
48
+ type: 'deletion',
49
+ code: 'T21652-',
50
+ },
51
+ count: 17169,
52
+ proportion: 0.16814713976514833,
53
+ },
54
+ ],
55
+ mutationOverTimeSerializedAsArray: {
56
+ data: [
57
+ [
58
+ 'A19722G',
59
+ [
60
+ [
61
+ '2024-01',
62
+ {
63
+ type: 'belowThreshold',
64
+ totalCount: 26387,
65
+ },
66
+ ],
67
+ [
68
+ '2024-02',
69
+ {
70
+ type: 'belowThreshold',
71
+ totalCount: 17340,
72
+ },
73
+ ],
74
+ [
75
+ '2024-03',
76
+ {
77
+ type: 'value',
78
+ count: 36,
79
+ proportion: 0.0043859649122807015,
80
+ totalCount: 8236,
81
+ },
82
+ ],
83
+ [
84
+ '2024-04',
85
+ {
86
+ type: 'value',
87
+ count: 0,
88
+ proportion: NaN,
89
+ totalCount: 0,
90
+ },
91
+ ],
92
+ [
93
+ '2024-05',
94
+ {
95
+ type: 'value',
96
+ count: 759,
97
+ proportion: 0.08675277174534232,
98
+ totalCount: 8799,
99
+ },
100
+ ],
101
+ [
102
+ '2024-06',
103
+ {
104
+ type: 'value',
105
+ count: 0,
106
+ proportion: NaN,
107
+ totalCount: 0,
108
+ },
109
+ ],
110
+ [
111
+ '2024-07',
112
+ {
113
+ type: 'value',
114
+ count: 6318,
115
+ proportion: 0.2995448511283899,
116
+ totalCount: 21234,
117
+ },
118
+ ],
119
+ ],
120
+ ],
121
+ [
122
+ 'G21641T',
123
+ [
124
+ [
125
+ '2024-01',
126
+ {
127
+ type: 'value',
128
+ count: 1625,
129
+ proportion: 0.07085858806087297,
130
+ totalCount: 26387,
131
+ },
132
+ ],
133
+ [
134
+ '2024-02',
135
+ {
136
+ type: 'value',
137
+ count: 419,
138
+ proportion: 0.027931471235250985,
139
+ totalCount: 17340,
140
+ },
141
+ ],
142
+ [
143
+ '2024-03',
144
+ {
145
+ type: 'value',
146
+ count: 228,
147
+ proportion: 0.03451930355791068,
148
+ totalCount: 8236,
149
+ },
150
+ ],
151
+ [
152
+ '2024-04',
153
+ {
154
+ type: 'value',
155
+ count: 0,
156
+ proportion: NaN,
157
+ totalCount: 0,
158
+ },
159
+ ],
160
+ [
161
+ '2024-05',
162
+ {
163
+ type: 'value',
164
+ count: 666,
165
+ proportion: 0.09498003422703936,
166
+ totalCount: 8799,
167
+ },
168
+ ],
169
+ [
170
+ '2024-06',
171
+ {
172
+ type: 'value',
173
+ count: 0,
174
+ proportion: NaN,
175
+ totalCount: 0,
176
+ },
177
+ ],
178
+ [
179
+ '2024-07',
180
+ {
181
+ type: 'value',
182
+ count: 270,
183
+ proportion: 0.013585589212035825,
184
+ totalCount: 21234,
185
+ },
186
+ ],
187
+ ],
188
+ ],
189
+ [
190
+ 'T21652-',
191
+ [
192
+ [
193
+ '2024-01',
194
+ {
195
+ type: 'belowThreshold',
196
+ totalCount: 26387,
197
+ },
198
+ ],
199
+ [
200
+ '2024-02',
201
+ {
202
+ type: 'belowThreshold',
203
+ totalCount: 17340,
204
+ },
205
+ ],
206
+ [
207
+ '2024-03',
208
+ {
209
+ type: 'value',
210
+ count: 40,
211
+ proportion: 0.004921259842519685,
212
+ totalCount: 8236,
213
+ },
214
+ ],
215
+ [
216
+ '2024-04',
217
+ {
218
+ type: 'value',
219
+ count: 0,
220
+ proportion: NaN,
221
+ totalCount: 0,
222
+ },
223
+ ],
224
+ [
225
+ '2024-05',
226
+ {
227
+ type: 'value',
228
+ count: 1304,
229
+ proportion: 0.151504589287789,
230
+ totalCount: 8799,
231
+ },
232
+ ],
233
+ [
234
+ '2024-06',
235
+ {
236
+ type: 'value',
237
+ count: 0,
238
+ proportion: NaN,
239
+ totalCount: 0,
240
+ },
241
+ ],
242
+ [
243
+ '2024-07',
244
+ {
245
+ type: 'value',
246
+ count: 10815,
247
+ proportion: 0.5221610660486674,
248
+ totalCount: 21234,
249
+ },
250
+ ],
251
+ ],
252
+ ],
253
+ ],
254
+ keysFirstAxis: [
255
+ [
256
+ 'A19722G',
257
+ {
258
+ type: 'substitution',
259
+ code: 'A19722G',
260
+ position: 19722,
261
+ valueAtReference: 'A',
262
+ substitutionValue: 'G',
263
+ },
264
+ ],
265
+ [
266
+ 'G21641T',
267
+ {
268
+ type: 'substitution',
269
+ code: 'G21641T',
270
+ position: 21641,
271
+ valueAtReference: 'G',
272
+ substitutionValue: 'T',
273
+ },
274
+ ],
275
+ [
276
+ 'T21652-',
277
+ {
278
+ type: 'deletion',
279
+ code: 'T21652-',
280
+ position: 21652,
281
+ valueAtReference: 'T',
282
+ },
283
+ ],
284
+ ],
285
+ keysSecondAxis: [
286
+ [
287
+ '2024-01',
288
+ {
289
+ type: 'YearMonth',
290
+ yearNumber: 2024,
291
+ monthNumber: 1,
292
+ dateString: '2024-01',
293
+ },
294
+ ],
295
+ [
296
+ '2024-02',
297
+ {
298
+ type: 'YearMonth',
299
+ yearNumber: 2024,
300
+ monthNumber: 2,
301
+ dateString: '2024-02',
302
+ },
303
+ ],
304
+ [
305
+ '2024-03',
306
+ {
307
+ type: 'YearMonth',
308
+ yearNumber: 2024,
309
+ monthNumber: 3,
310
+ dateString: '2024-03',
311
+ },
312
+ ],
313
+ [
314
+ '2024-04',
315
+ {
316
+ type: 'YearMonth',
317
+ yearNumber: 2024,
318
+ monthNumber: 4,
319
+ dateString: '2024-04',
320
+ },
321
+ ],
322
+ [
323
+ '2024-05',
324
+ {
325
+ type: 'YearMonth',
326
+ yearNumber: 2024,
327
+ monthNumber: 5,
328
+ dateString: '2024-05',
329
+ },
330
+ ],
331
+ [
332
+ '2024-06',
333
+ {
334
+ type: 'YearMonth',
335
+ yearNumber: 2024,
336
+ monthNumber: 6,
337
+ dateString: '2024-06',
338
+ },
339
+ ],
340
+ [
341
+ '2024-07',
342
+ {
343
+ type: 'YearMonth',
344
+ yearNumber: 2024,
345
+ monthNumber: 7,
346
+ dateString: '2024-07',
347
+ },
348
+ ],
349
+ ],
350
+ },
351
+ },
352
+ };
@@ -27,6 +27,7 @@ describe('getFilteredMutationOverTimeData', () => {
27
27
  ],
28
28
  displayedMutationTypes: [],
29
29
  proportionInterval,
30
+ hideGaps: false,
30
31
  displayMutations: undefined,
31
32
  mutationFilterValue: { textFilter: '', annotationNameFilter: new Set() },
32
33
  sequenceType: 'nucleotide',
@@ -62,6 +63,7 @@ describe('getFilteredMutationOverTimeData', () => {
62
63
  },
63
64
  ],
64
65
  proportionInterval,
66
+ hideGaps: false,
65
67
  mutationFilterValue: { textFilter: '', annotationNameFilter: new Set() },
66
68
  sequenceType: 'nucleotide',
67
69
  annotationProvider: () => {
@@ -85,6 +87,7 @@ describe('getFilteredMutationOverTimeData', () => {
85
87
  displayedSegments: [],
86
88
  displayedMutationTypes: [],
87
89
  proportionInterval,
90
+ hideGaps: false,
88
91
  mutationFilterValue: { textFilter: '', annotationNameFilter: new Set() },
89
92
  sequenceType: 'nucleotide',
90
93
  annotationProvider: () => {
@@ -108,6 +111,7 @@ describe('getFilteredMutationOverTimeData', () => {
108
111
  displayedSegments: [],
109
112
  displayedMutationTypes: [],
110
113
  proportionInterval,
114
+ hideGaps: false,
111
115
  mutationFilterValue: { textFilter: '', annotationNameFilter: new Set() },
112
116
  sequenceType: 'nucleotide',
113
117
  annotationProvider: () => {
@@ -132,6 +136,7 @@ describe('getFilteredMutationOverTimeData', () => {
132
136
  displayedSegments: [],
133
137
  displayedMutationTypes: [],
134
138
  proportionInterval,
139
+ hideGaps: false,
135
140
  mutationFilterValue: { textFilter: '', annotationNameFilter: new Set() },
136
141
  sequenceType: 'nucleotide',
137
142
  annotationProvider: () => {
@@ -156,6 +161,7 @@ describe('getFilteredMutationOverTimeData', () => {
156
161
  displayedSegments: [],
157
162
  displayedMutationTypes: [],
158
163
  proportionInterval,
164
+ hideGaps: false,
159
165
  mutationFilterValue: { textFilter: '', annotationNameFilter: new Set() },
160
166
  sequenceType: 'nucleotide',
161
167
  annotationProvider: () => {
@@ -178,6 +184,7 @@ describe('getFilteredMutationOverTimeData', () => {
178
184
  displayedSegments: [],
179
185
  displayedMutationTypes: [],
180
186
  proportionInterval,
187
+ hideGaps: false,
181
188
  mutationFilterValue: { textFilter: '', annotationNameFilter: new Set() },
182
189
  sequenceType: 'nucleotide',
183
190
  annotationProvider: () => {
@@ -201,6 +208,7 @@ describe('getFilteredMutationOverTimeData', () => {
201
208
  displayedSegments: [],
202
209
  displayedMutationTypes: [],
203
210
  proportionInterval,
211
+ hideGaps: false,
204
212
  mutationFilterValue: { textFilter: '', annotationNameFilter: new Set() },
205
213
  sequenceType: 'nucleotide',
206
214
  annotationProvider: () => {
@@ -224,6 +232,7 @@ describe('getFilteredMutationOverTimeData', () => {
224
232
  displayedSegments: [],
225
233
  displayedMutationTypes: [],
226
234
  proportionInterval,
235
+ hideGaps: false,
227
236
  mutationFilterValue: { textFilter: '23T', annotationNameFilter: new Set() },
228
237
  sequenceType: 'nucleotide',
229
238
  annotationProvider: () => {
@@ -250,6 +259,7 @@ describe('getFilteredMutationOverTimeData', () => {
250
259
  displayedSegments: [],
251
260
  displayedMutationTypes: [],
252
261
  proportionInterval,
262
+ hideGaps: false,
253
263
  mutationFilterValue: filterValue,
254
264
  sequenceType: 'nucleotide',
255
265
  annotationProvider,
@@ -303,6 +313,28 @@ describe('getFilteredMutationOverTimeData', () => {
303
313
  });
304
314
  });
305
315
 
316
+ it('should remove date ranges that have no samples', () => {
317
+ const { data, overallMutationData } = prepareMutationOverTimeData([someSubstitutionEntry, someDeletionEntry]);
318
+ data.set(someSubstitution, anotherTemporal, emptyMutationOverTimeValue);
319
+ data.set(someDeletion, anotherTemporal, emptyMutationOverTimeValue);
320
+
321
+ const result = getFilteredMutationOverTimeData({
322
+ data,
323
+ overallMutationData,
324
+ displayedSegments: [],
325
+ displayedMutationTypes: [],
326
+ proportionInterval,
327
+ hideGaps: true,
328
+ mutationFilterValue: { textFilter: '', annotationNameFilter: new Set() },
329
+ sequenceType: 'nucleotide',
330
+ annotationProvider: () => {
331
+ return [];
332
+ },
333
+ });
334
+
335
+ expect(result.getSecondAxisKeys()).to.deep.equal([someTemporal]);
336
+ });
337
+
306
338
  const belowFilter = 0.1;
307
339
  const atFilterMin = 0.2;
308
340
  const inFilter = 0.5;
@@ -361,6 +393,12 @@ describe('getFilteredMutationOverTimeData', () => {
361
393
  proportion: inFilter,
362
394
  totalCount: 10,
363
395
  } satisfies MutationOverTimeMutationValue;
396
+ const emptyMutationOverTimeValue = {
397
+ type: 'value',
398
+ count: 0,
399
+ proportion: NaN,
400
+ totalCount: 0,
401
+ } satisfies MutationOverTimeMutationValue;
364
402
 
365
403
  function prepareMutationOverTimeData(
366
404
  mutationEntries: (SubstitutionEntry<Substitution> | DeletionEntry<Deletion>)[],
@@ -22,6 +22,7 @@ export type GetFilteredMutationOverTimeDataArgs = {
22
22
  displayedSegments: DisplayedSegment[];
23
23
  displayedMutationTypes: DisplayedMutationType[];
24
24
  proportionInterval: { min: number; max: number };
25
+ hideGaps: boolean;
25
26
  displayMutations?: DisplayMutations;
26
27
  mutationFilterValue: MutationFilter;
27
28
  sequenceType: SequenceType;
@@ -34,6 +35,7 @@ export function getFilteredMutationOverTimeData({
34
35
  displayedSegments,
35
36
  displayedMutationTypes,
36
37
  proportionInterval,
38
+ hideGaps,
37
39
  mutationFilterValue,
38
40
  sequenceType,
39
41
  annotationProvider,
@@ -63,6 +65,14 @@ export function getFilteredMutationOverTimeData({
63
65
  filteredData.deleteRow(entry.mutation);
64
66
  });
65
67
 
68
+ if (hideGaps) {
69
+ const dateRangesToFilterOut = filteredData.getSecondAxisKeys().filter((dateRange) => {
70
+ const vals = filteredData.getColumn(dateRange);
71
+ return !vals.some((v) => v?.type === 'value' && v.totalCount > 0);
72
+ });
73
+ dateRangesToFilterOut.forEach((dateRange) => filteredData.deleteColumn(dateRange));
74
+ }
75
+
66
76
  return filteredData;
67
77
  }
68
78
 
@@ -8,6 +8,7 @@ import { getMutationOverTimeMock } from './__mockData__/mockConversion';
8
8
  import { noDataWhenNoMutationsAreInFilter } from './__mockData__/noDataWhenNoMutationsAreInFilter';
9
9
  import { showsMessageWhenTooManyMutations } from './__mockData__/showsMessageWhenTooManyMutations';
10
10
  import { withDisplayMutations } from './__mockData__/withDisplayMutations';
11
+ import { withGaps } from './__mockData__/withGaps';
11
12
 
12
13
  const mockQueries: { query: MutationOverTimeQuery; response: MutationOverTimeWorkerResponse }[] = [
13
14
  getMutationOverTimeMock(defaultMockData),
@@ -16,6 +17,7 @@ const mockQueries: { query: MutationOverTimeQuery; response: MutationOverTimeWor
16
17
  getMutationOverTimeMock(withDisplayMutations),
17
18
  getMutationOverTimeMock(aminoAcidMutationsByDay),
18
19
  getMutationOverTimeMock(noDataWhenNoMutationsAreInFilter),
20
+ getMutationOverTimeMock(withGaps),
19
21
  ];
20
22
 
21
23
  self.onmessage = async function (event: MessageEvent<MutationOverTimeQuery>) {
@@ -35,6 +35,7 @@ const meta: Meta<MutationsOverTimeProps> = {
35
35
  lapisDateField: { control: 'text' },
36
36
  displayMutations: { control: 'object' },
37
37
  initialMeanProportionInterval: { control: 'object' },
38
+ hideGaps: { control: 'boolean' },
38
39
  pageSizes: { control: 'object' },
39
40
  useNewEndpoint: { control: 'boolean' },
40
41
  },
@@ -81,6 +82,7 @@ export const Default: StoryObj<MutationsOverTimeProps> = {
81
82
  granularity: 'month',
82
83
  lapisDateField: 'date',
83
84
  initialMeanProportionInterval: { min: 0.05, max: 0.9 },
85
+ hideGaps: false,
84
86
  useNewEndpoint: false,
85
87
  pageSizes: [10, 20, 30, 40, 50],
86
88
  },
@@ -113,6 +115,32 @@ export const ShowsNoDataWhenNoMutationsAreInFilter: StoryObj<MutationsOverTimePr
113
115
  },
114
116
  };
115
117
 
118
+ export const UsesHideGaps: StoryObj<MutationsOverTimeProps> = {
119
+ ...Default,
120
+ args: {
121
+ ...Default.args,
122
+ displayMutations: ['A19722G', 'G21641T', 'T21652-'],
123
+ },
124
+ play: async ({ canvas, step }) => {
125
+ await expectDateRangeOnPage(canvas, '2024-04');
126
+
127
+ await step('hide gaps', async () => {
128
+ const hideGapsButton = canvas.getByRole('button', { name: 'Hide gaps' });
129
+ await userEvent.click(hideGapsButton);
130
+ });
131
+
132
+ const filteredDateRange = canvas.queryByText('2024-04');
133
+ await expect(filteredDateRange).not.toBeInTheDocument();
134
+
135
+ await step('un-hide gaps', async () => {
136
+ const hideGapsButton = canvas.getByRole('button', { name: 'Gaps hidden' });
137
+ await userEvent.click(hideGapsButton);
138
+ });
139
+
140
+ await expectDateRangeOnPage(canvas, '2024-04');
141
+ },
142
+ };
143
+
116
144
  export const UsesPagination: StoryObj<MutationsOverTimeProps> = {
117
145
  ...Default,
118
146
  play: async ({ canvas, step }) => {
@@ -175,6 +203,13 @@ async function expectMutationOnPage(canvas: Canvas, mutation: string) {
175
203
  });
176
204
  }
177
205
 
206
+ async function expectDateRangeOnPage(canvas: Canvas, dateRange: string) {
207
+ await waitFor(async () => {
208
+ const dateRangeOnFirstPage = canvas.getAllByText(dateRange)[0];
209
+ await expect(dateRangeOnFirstPage).toBeVisible();
210
+ });
211
+ }
212
+
178
213
  export const ShowsNoDataMessageWhenThereAreNoDatesInFilter: StoryObj<MutationsOverTimeProps> = {
179
214
  ...Default,
180
215
  args: {
@@ -1,5 +1,5 @@
1
1
  import { type FunctionComponent } from 'preact';
2
- import { type Dispatch, type StateUpdater, useMemo, useState } from 'preact/hooks';
2
+ import { type Dispatch, type StateUpdater, useMemo, useState, useEffect } from 'preact/hooks';
3
3
  import z from 'zod';
4
4
 
5
5
  // @ts-expect-error -- uses subpath imports and vite worker import
@@ -59,6 +59,7 @@ const mutationOverTimeSchema = z.object({
59
59
  min: z.number().min(0).max(1),
60
60
  max: z.number().min(0).max(1),
61
61
  }),
62
+ hideGaps: z.boolean().optional(),
62
63
  width: z.string(),
63
64
  height: z.string().optional(),
64
65
  pageSizes: pageSizesSchema,
@@ -154,6 +155,10 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
154
155
  { label: 'Deletions', checked: true, type: 'deletion' },
155
156
  ]);
156
157
 
158
+ const [hideGaps, setHideGaps] = useState<boolean>(originalComponentProps.hideGaps ?? false);
159
+
160
+ useEffect(() => setHideGaps(originalComponentProps.hideGaps ?? false), [originalComponentProps.hideGaps]);
161
+
157
162
  const filteredData = useMemo(() => {
158
163
  return getFilteredMutationOverTimeData({
159
164
  data: mutationOverTimeData,
@@ -161,6 +166,7 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
161
166
  displayedSegments,
162
167
  displayedMutationTypes,
163
168
  proportionInterval,
169
+ hideGaps,
164
170
  mutationFilterValue,
165
171
  sequenceType: originalComponentProps.sequenceType,
166
172
  annotationProvider,
@@ -171,6 +177,7 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
171
177
  displayedSegments,
172
178
  displayedMutationTypes,
173
179
  proportionInterval,
180
+ hideGaps,
174
181
  originalComponentProps.sequenceType,
175
182
  mutationFilterValue,
176
183
  annotationProvider,
@@ -205,6 +212,8 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
205
212
  setDisplayedMutationTypes={setDisplayedMutationTypes}
206
213
  proportionInterval={proportionInterval}
207
214
  setProportionInterval={setProportionInterval}
215
+ hideGaps={hideGaps}
216
+ setHideGaps={setHideGaps}
208
217
  filteredData={filteredData}
209
218
  colorScale={colorScale}
210
219
  setColorScale={setColorScale}
@@ -229,6 +238,8 @@ type ToolbarProps = {
229
238
  setDisplayedMutationTypes: (types: DisplayedMutationType[]) => void;
230
239
  proportionInterval: ProportionInterval;
231
240
  setProportionInterval: Dispatch<StateUpdater<ProportionInterval>>;
241
+ hideGaps: boolean;
242
+ setHideGaps: Dispatch<StateUpdater<boolean>>;
232
243
  filteredData: MutationOverTimeDataMap;
233
244
  colorScale: ColorScale;
234
245
  setColorScale: Dispatch<StateUpdater<ColorScale>>;
@@ -245,6 +256,8 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
245
256
  setDisplayedMutationTypes,
246
257
  proportionInterval,
247
258
  setProportionInterval,
259
+ hideGaps,
260
+ setHideGaps,
248
261
  filteredData,
249
262
  colorScale,
250
263
  setColorScale,
@@ -255,9 +268,6 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
255
268
  return (
256
269
  <>
257
270
  <MutationsOverTimeMutationsFilter setFilterValue={setFilterValue} value={mutationFilterValue} />
258
- {activeTab === 'Grid' && (
259
- <ColorScaleSelectorDropdown colorScale={colorScale} setColorScale={setColorScale} />
260
- )}
261
271
  <SegmentSelector
262
272
  displayedSegments={displayedSegments}
263
273
  setDisplayedSegments={setDisplayedSegments}
@@ -273,6 +283,20 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
273
283
  setMaxProportion={(max) => setProportionInterval((prev) => ({ ...prev, max }))}
274
284
  labelPrefix='Mean proportion'
275
285
  />
286
+ <button
287
+ className='btn btn-xs w-24'
288
+ onClick={() => setHideGaps((s) => !s)}
289
+ title={
290
+ hideGaps
291
+ ? 'Date ranges that do not contain data are excluded from the table'
292
+ : 'Exclude date ranges without data from the table'
293
+ }
294
+ >
295
+ {hideGaps ? 'Gaps hidden' : 'Hide gaps'}
296
+ </button>
297
+ {activeTab === 'Grid' && (
298
+ <ColorScaleSelectorDropdown colorScale={colorScale} setColorScale={setColorScale} />
299
+ )}
276
300
  <CsvDownloadButton
277
301
  className='btn btn-xs'
278
302
  getData={() => getDownloadData(filteredData)}