@genspectrum/dashboard-components 0.18.0 → 0.18.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.
Files changed (35) hide show
  1. package/custom-elements.json +7 -7
  2. package/dist/{LineageFilterChangedEvent-DkvWdq_G.js → LineageFilterChangedEvent-ixHQkq8y.js} +2 -2
  3. package/dist/{LineageFilterChangedEvent-DkvWdq_G.js.map → LineageFilterChangedEvent-ixHQkq8y.js.map} +1 -1
  4. package/dist/assets/{mutationOverTimeWorker-CPfQDLe6.js.map → mutationOverTimeWorker-ChQTFL68.js.map} +1 -1
  5. package/dist/components.d.ts +33 -31
  6. package/dist/components.js +114 -63
  7. package/dist/components.js.map +1 -1
  8. package/dist/util.d.ts +28 -28
  9. package/dist/util.js +1 -1
  10. package/package.json +1 -1
  11. package/src/preact/components/color-scale-selector.tsx +1 -1
  12. package/src/preact/dateRangeFilter/computeInitialValues.spec.ts +2 -2
  13. package/src/preact/dateRangeFilter/computeInitialValues.ts +1 -1
  14. package/src/preact/dateRangeFilter/date-range-filter.stories.tsx +3 -5
  15. package/src/preact/dateRangeFilter/date-range-filter.tsx +12 -7
  16. package/src/preact/dateRangeFilter/dateRangeOption.ts +1 -1
  17. package/src/preact/mutationFilter/mutation-filter.tsx +1 -1
  18. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutationsByDay.ts +3773 -2290
  19. package/src/preact/mutationsOverTime/__mockData__/byWeek.ts +3012 -948
  20. package/src/preact/mutationsOverTime/__mockData__/defaultMockData.ts +8799 -4406
  21. package/src/preact/mutationsOverTime/__mockData__/showsMessageWhenTooManyMutations.ts +1836 -0
  22. package/src/preact/mutationsOverTime/getFilteredMutationsOverTime.spec.ts +3 -1
  23. package/src/preact/mutationsOverTime/mutations-over-time-grid-tooltip.stories.tsx +108 -0
  24. package/src/preact/mutationsOverTime/mutations-over-time-grid-tooltip.tsx +93 -0
  25. package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +12 -48
  26. package/src/preact/mutationsOverTime/mutations-over-time.tsx +2 -1
  27. package/src/preact/wastewater/mutationsOverTime/computeWastewaterMutationsOverTimeDataPerLocation.spec.ts +5 -5
  28. package/src/preact/wastewater/mutationsOverTime/computeWastewaterMutationsOverTimeDataPerLocation.ts +1 -3
  29. package/src/query/queryMutationsOverTime.spec.ts +28 -26
  30. package/src/query/queryMutationsOverTime.ts +20 -8
  31. package/src/web-components/input/gs-date-range-filter.stories.ts +1 -1
  32. package/src/web-components/input/gs-date-range-filter.tsx +7 -5
  33. package/standalone-bundle/assets/{mutationOverTimeWorker-CERZSdcA.js.map → mutationOverTimeWorker-jChgWnwp.js.map} +1 -1
  34. package/standalone-bundle/dashboard-components.js +2189 -2149
  35. package/standalone-bundle/dashboard-components.js.map +1 -1
@@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest';
2
2
 
3
3
  import { BaseMutationOverTimeDataMap } from './MutationOverTimeData';
4
4
  import { getFilteredMutationOverTimeData } from './getFilteredMutationsOverTimeData';
5
+ import { type MutationOverTimeMutationValue } from '../../query/queryMutationsOverTime';
5
6
  import { type DeletionEntry, type SubstitutionEntry } from '../../types';
6
7
  import { type Deletion, type Substitution } from '../../utils/mutations';
7
8
  import { type TemporalClass } from '../../utils/temporalClass';
@@ -240,10 +241,11 @@ describe('getFilteredMutationOverTimeData', () => {
240
241
  const someTemporal = yearMonthDay('2021-01-01');
241
242
  const anotherTemporal = yearMonthDay('2021-02-02');
242
243
  const someMutationOverTimeValue = {
244
+ type: 'value',
243
245
  count: 1,
244
246
  proportion: inFilter,
245
247
  totalCount: 10,
246
- };
248
+ } satisfies MutationOverTimeMutationValue;
247
249
 
248
250
  function prepareMutationOverTimeData(
249
251
  mutationEntries: (SubstitutionEntry<Substitution> | DeletionEntry<Deletion>)[],
@@ -0,0 +1,108 @@
1
+ import { type Meta, type StoryObj } from '@storybook/preact';
2
+ import { expect, within } from '@storybook/test';
3
+
4
+ import {
5
+ MutationsOverTimeGridTooltip,
6
+ type MutationsOverTimeGridTooltipProps,
7
+ } from './mutations-over-time-grid-tooltip';
8
+
9
+ const meta: Meta<MutationsOverTimeGridTooltipProps> = {
10
+ title: 'Component/Mutation over time grid tooltip',
11
+ component: MutationsOverTimeGridTooltip,
12
+ argTypes: {
13
+ mutation: { control: 'object' },
14
+ date: { control: 'object' },
15
+ value: { control: 'object' },
16
+ },
17
+ parameters: {
18
+ fetchMock: {},
19
+ },
20
+ };
21
+
22
+ export default meta;
23
+
24
+ const Template: StoryObj<MutationsOverTimeGridTooltipProps> = {
25
+ render: (args: MutationsOverTimeGridTooltipProps) => <MutationsOverTimeGridTooltip {...args} />,
26
+ args: {
27
+ mutation: {
28
+ type: 'deletion',
29
+ position: 500,
30
+ code: 'A500-',
31
+ valueAtReference: 'A',
32
+ },
33
+ date: {
34
+ type: 'Year',
35
+ year: 2025,
36
+ dateString: '2025',
37
+ },
38
+ value: null,
39
+ },
40
+ };
41
+
42
+ export const NoValue: StoryObj<MutationsOverTimeGridTooltipProps> = {
43
+ ...Template,
44
+ play: async ({ canvasElement }) => {
45
+ const canvas = within(canvasElement);
46
+
47
+ await expect(canvas.getByText('2025', { exact: true })).toBeVisible();
48
+ await expect(canvas.getByText('(2025-01-01 - 2025-12-31)')).toBeVisible();
49
+ await expect(canvas.getByText('A500-')).toBeVisible();
50
+ await expect(canvas.getByText('No data')).toBeVisible();
51
+ },
52
+ };
53
+
54
+ export const WithValue: StoryObj<MutationsOverTimeGridTooltipProps> = {
55
+ ...Template,
56
+ args: {
57
+ ...Template.args,
58
+ value: {
59
+ type: 'value',
60
+ proportion: 0.5,
61
+ count: 100,
62
+ totalCount: 300,
63
+ },
64
+ },
65
+ play: async ({ canvasElement }) => {
66
+ const canvas = within(canvasElement);
67
+
68
+ await expect(canvas.getByText('Proportion: 50.00%')).toBeVisible();
69
+ await expect(canvas.getByText('300 samples are in the timeframe')).toBeVisible();
70
+ await expect(canvas.getByText('200 have coverage, of those 100 have the mutation')).toBeVisible();
71
+ },
72
+ };
73
+
74
+ export const WithValueBelowThreshold: StoryObj<MutationsOverTimeGridTooltipProps> = {
75
+ ...Template,
76
+ args: {
77
+ ...Template.args,
78
+ value: {
79
+ type: 'belowThreshold',
80
+ totalCount: 300,
81
+ },
82
+ },
83
+ play: async ({ canvasElement }) => {
84
+ const canvas = within(canvasElement);
85
+
86
+ await expect(canvas.getByText('Proportion: <0.10%')).toBeVisible();
87
+ await expect(canvas.getByText('300 samples are in the timeframe')).toBeVisible();
88
+ await expect(canvas.getByText('none or less than 0.10% have the mutation')).toBeVisible();
89
+ },
90
+ };
91
+
92
+ export const WithWastewaterValue: StoryObj<MutationsOverTimeGridTooltipProps> = {
93
+ ...Template,
94
+ args: {
95
+ ...Template.args,
96
+ value: {
97
+ type: 'wastewaterValue',
98
+ proportion: 0.5,
99
+ },
100
+ },
101
+ play: async ({ canvasElement }) => {
102
+ const canvas = within(canvasElement);
103
+
104
+ await expect(canvas.getByText('Proportion: 50.00%')).toBeVisible();
105
+ await expect(canvas.queryByText('samples are in the timeframe')).not.toBeInTheDocument();
106
+ await expect(canvas.queryByText('have coverage')).not.toBeInTheDocument();
107
+ },
108
+ };
@@ -0,0 +1,93 @@
1
+ import type { FunctionComponent } from 'preact';
2
+
3
+ import {
4
+ type MutationOverTimeMutationValue,
5
+ MUTATIONS_OVER_TIME_MIN_PROPORTION,
6
+ } from '../../query/queryMutationsOverTime';
7
+ import type { Deletion, Substitution } from '../../utils/mutations';
8
+ import { type Temporal, type TemporalClass, toTemporalClass, YearMonthDayClass } from '../../utils/temporalClass';
9
+ import { formatProportion } from '../shared/table/formatProportion';
10
+
11
+ export type MutationsOverTimeGridTooltipProps = {
12
+ mutation: Substitution | Deletion;
13
+ date: Temporal;
14
+ value: MutationOverTimeMutationValue;
15
+ };
16
+
17
+ export const MutationsOverTimeGridTooltip: FunctionComponent<MutationsOverTimeGridTooltipProps> = ({
18
+ mutation,
19
+ date,
20
+ value,
21
+ }: MutationsOverTimeGridTooltipProps) => {
22
+ const dateClass = toTemporalClass(date);
23
+
24
+ return (
25
+ <div>
26
+ <p>
27
+ <span className='font-bold'>{dateClass.englishName()}</span>
28
+ </p>
29
+ <p>({timeIntervalDisplay(dateClass)})</p>
30
+ <p>{mutation.code}</p>
31
+ <TooltipValueDescription value={value} />
32
+ </div>
33
+ );
34
+ };
35
+
36
+ const TooltipValueDescription: FunctionComponent<{ value: MutationOverTimeMutationValue }> = ({ value }) => {
37
+ if (value === null) {
38
+ return <p>No data</p>;
39
+ }
40
+
41
+ const proportion =
42
+ value.type === 'belowThreshold'
43
+ ? `<${formatProportion(MUTATIONS_OVER_TIME_MIN_PROPORTION)}`
44
+ : formatProportion(value.proportion);
45
+
46
+ return (
47
+ <>
48
+ <p>Proportion: {proportion}</p>
49
+ <TooltipValueCountsDescription value={value} />
50
+ </>
51
+ );
52
+ };
53
+
54
+ const TooltipValueCountsDescription: FunctionComponent<{
55
+ value: NonNullable<MutationOverTimeMutationValue>;
56
+ }> = ({ value }) => {
57
+ switch (value.type) {
58
+ case 'wastewaterValue':
59
+ return;
60
+ case 'belowThreshold':
61
+ return (
62
+ <>
63
+ <p>{value.totalCount} samples are in the timeframe</p>
64
+ <p>none or less than {formatProportion(MUTATIONS_OVER_TIME_MIN_PROPORTION)} have the mutation</p>
65
+ </>
66
+ );
67
+ case 'value':
68
+ return (
69
+ <>
70
+ <p>{value.totalCount} samples are in the timeframe</p>
71
+ <p>
72
+ {totalCountWithCoverage(value.count, value.proportion)} have coverage, of those {value.count}{' '}
73
+ have the mutation
74
+ </p>
75
+ </>
76
+ );
77
+ }
78
+ };
79
+
80
+ function totalCountWithCoverage(count: number, proportion: number) {
81
+ if (count === 0) {
82
+ return 0;
83
+ }
84
+ return Math.round(count / proportion);
85
+ }
86
+
87
+ const timeIntervalDisplay = (date: TemporalClass) => {
88
+ if (date instanceof YearMonthDayClass) {
89
+ return date.toString();
90
+ }
91
+
92
+ return `${date.firstDay.toString()} - ${date.lastDay.toString()}`;
93
+ };
@@ -3,12 +3,13 @@ import { type FunctionComponent } from 'preact';
3
3
  import { useMemo, useState } from 'preact/hooks';
4
4
 
5
5
  import { type MutationOverTimeDataMap } from './MutationOverTimeData';
6
+ import { MutationsOverTimeGridTooltip } from './mutations-over-time-grid-tooltip';
6
7
  import { type MutationOverTimeMutationValue } from '../../query/queryMutationsOverTime';
7
8
  import { type SequenceType } from '../../types';
8
9
  import { type Deletion, type Substitution } from '../../utils/mutations';
9
- import { type Temporal, type TemporalClass, toTemporalClass, YearMonthDayClass } from '../../utils/temporalClass';
10
+ import { type Temporal } from '../../utils/temporalClass';
10
11
  import { AnnotatedMutation } from '../components/annotated-mutation';
11
- import { type ColorScale, getColorWithingScale, getTextColorForScale } from '../components/color-scale-selector';
12
+ import { type ColorScale, getColorWithinScale, getTextColorForScale } from '../components/color-scale-selector';
12
13
  import Tooltip, { type TooltipPosition } from '../components/tooltip';
13
14
  import { formatProportion } from '../shared/table/formatProportion';
14
15
  import { type PageSizes, Pagination } from '../shared/tanstackTable/pagination';
@@ -178,47 +179,25 @@ const ProportionCell: FunctionComponent<{
178
179
  tooltipPosition: TooltipPosition;
179
180
  colorScale: ColorScale;
180
181
  }> = ({ value, mutation, date, tooltipPosition, colorScale }) => {
181
- const dateClass = toTemporalClass(date);
182
-
183
- const tooltipContent = (
184
- <div>
185
- <p>
186
- <span className='font-bold'>{dateClass.englishName()}</span>
187
- </p>
188
- <p>({timeIntervalDisplay(dateClass)})</p>
189
- <p>{mutation.code}</p>
190
- {value === null ? (
191
- <p>No data</p>
192
- ) : (
193
- <>
194
- <p>Proportion: {formatProportion(value.proportion)}</p>
195
- {value.count !== null && value.totalCount !== null && (
196
- <>
197
- <p>
198
- {value.count} / {totalCountWithCoverage(value.count, value.proportion)} with coverage
199
- </p>
200
- <p>{value.totalCount} in timeframe</p>
201
- </>
202
- )}
203
- </>
204
- )}
205
- </div>
206
- );
182
+ const proportion = value?.type === 'belowThreshold' ? 0 : value?.proportion;
207
183
 
208
184
  return (
209
185
  <div className={'py-1 w-full h-full'}>
210
- <Tooltip content={tooltipContent} position={tooltipPosition}>
186
+ <Tooltip
187
+ content={<MutationsOverTimeGridTooltip mutation={mutation} date={date} value={value} />}
188
+ position={tooltipPosition}
189
+ >
211
190
  <div
212
191
  style={{
213
- backgroundColor: getColorWithingScale(value?.proportion, colorScale),
214
- color: getTextColorForScale(value?.proportion, colorScale),
192
+ backgroundColor: getColorWithinScale(proportion, colorScale),
193
+ color: getTextColorForScale(proportion, colorScale),
215
194
  }}
216
195
  className={`w-full h-full hover:font-bold text-xs group @container`}
217
196
  >
218
197
  {value === null ? (
219
- <span className={'invisible'}>No data</span>
198
+ <span className='invisible'>No data</span>
220
199
  ) : (
221
- <span className='invisible @[2rem]:visible'>{formatProportion(value.proportion, 0)}</span>
200
+ <span className='invisible @[2rem]:visible'>{formatProportion(proportion ?? 0, 0)}</span>
222
201
  )}
223
202
  </div>
224
203
  </Tooltip>
@@ -226,19 +205,4 @@ const ProportionCell: FunctionComponent<{
226
205
  );
227
206
  };
228
207
 
229
- function totalCountWithCoverage(count: number, proportion: number) {
230
- if (count === 0) {
231
- return 0;
232
- }
233
- return Math.round(count / proportion);
234
- }
235
-
236
- const timeIntervalDisplay = (date: TemporalClass) => {
237
- if (date instanceof YearMonthDayClass) {
238
- return date.toString();
239
- }
240
-
241
- return `${date.firstDay.toString()} - ${date.lastDay.toString()}`;
242
- };
243
-
244
208
  export default MutationsOverTimeGrid;
@@ -283,7 +283,8 @@ function getDownloadData(filteredData: MutationOverTimeDataMap) {
283
283
  return filteredData.getFirstAxisKeys().map((mutation) => {
284
284
  return dates.reduce(
285
285
  (accumulated, date) => {
286
- const proportion = filteredData.get(mutation, date)?.proportion ?? '';
286
+ const value = filteredData.get(mutation, date);
287
+ const proportion = value?.type === 'value' || value?.type === 'wastewaterValue' ? value.proportion : '';
287
288
  return {
288
289
  ...accumulated,
289
290
  [date.dateString]: proportion,
@@ -59,10 +59,10 @@ describe('groupMutationDataByLocation', () => {
59
59
  temporalCache.getYearMonthDay('2025-01-02'),
60
60
  ]);
61
61
  expect(location1Data.getAsArray()).to.deep.equal([
62
- [{ count: null, proportion: 0.1, totalCount: null }, null],
62
+ [{ type: 'wastewaterValue', proportion: 0.1 }, null],
63
63
  [
64
- { count: null, proportion: 0.2, totalCount: null },
65
- { count: null, proportion: 0.3, totalCount: null },
64
+ { type: 'wastewaterValue', proportion: 0.2 },
65
+ { type: 'wastewaterValue', proportion: 0.3 },
66
66
  ],
67
67
  ]);
68
68
  });
@@ -96,8 +96,8 @@ describe('groupMutationDataByLocation', () => {
96
96
  expect(location1Data.getFirstAxisKeys()).to.deep.equal([mutation2, mutation3]);
97
97
  expect(location1Data.getSecondAxisKeys()).to.deep.equal([temporalCache.getYearMonthDay('2025-01-01')]);
98
98
  expect(location1Data.getAsArray()).to.deep.equal([
99
- [{ count: null, proportion: 0.2, totalCount: null }],
100
- [{ count: null, proportion: 0.3, totalCount: null }],
99
+ [{ type: 'wastewaterValue', proportion: 0.2 }],
100
+ [{ type: 'wastewaterValue', proportion: 0.3 }],
101
101
  ]);
102
102
  });
103
103
 
@@ -33,9 +33,7 @@ export function groupMutationDataByLocation(data: WastewaterData, sequenceType:
33
33
  map.set(
34
34
  mutation.mutation,
35
35
  row.date,
36
- mutation.proportion !== null
37
- ? { proportion: mutation.proportion, count: null, totalCount: null }
38
- : null,
36
+ mutation.proportion !== null ? { type: 'wastewaterValue', proportion: mutation.proportion } : null,
39
37
  );
40
38
  }
41
39
  }
@@ -101,14 +101,14 @@ describe('queryMutationsOverTime', () => {
101
101
 
102
102
  expect(mutationOverTimeData.getAsArray()).to.deep.equal([
103
103
  [
104
- { proportion: 0.4, count: 4, totalCount: 11 },
105
- { proportion: 0, count: 0, totalCount: 12 },
106
- { proportion: 0, count: 0, totalCount: 13 },
104
+ { type: 'value', proportion: 0.4, count: 4, totalCount: 11 },
105
+ { type: 'belowThreshold', totalCount: 12 },
106
+ { type: 'belowThreshold', totalCount: 13 },
107
107
  ],
108
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 },
109
+ { type: 'value', proportion: 0.1, count: 1, totalCount: 11 },
110
+ { type: 'value', proportion: 0.2, count: 2, totalCount: 12 },
111
+ { type: 'value', proportion: 0.3, count: 3, totalCount: 13 },
112
112
  ],
113
113
  ]);
114
114
 
@@ -251,8 +251,16 @@ describe('queryMutationsOverTime', () => {
251
251
  });
252
252
 
253
253
  expect(mutationOverTimeData.getAsArray()).to.deep.equal([
254
- [{ proportion: 0.4, count: 4, totalCount: 11 }, null, { proportion: 0, count: 0, totalCount: 13 }],
255
- [{ proportion: 0.1, count: 1, totalCount: 11 }, null, { proportion: 0.3, count: 3, totalCount: 13 }],
254
+ [
255
+ { type: 'value', proportion: 0.4, count: 4, totalCount: 11 },
256
+ null,
257
+ { type: 'belowThreshold', totalCount: 13 },
258
+ ],
259
+ [
260
+ { type: 'value', proportion: 0.1, count: 1, totalCount: 11 },
261
+ null,
262
+ { type: 'value', proportion: 0.3, count: 3, totalCount: 13 },
263
+ ],
256
264
  ]);
257
265
 
258
266
  const sequences = mutationOverTimeData.getFirstAxisKeys();
@@ -444,8 +452,8 @@ describe('queryMutationsOverTime', () => {
444
452
 
445
453
  expect(mutationOverTimeData.getAsArray()).to.deep.equal([
446
454
  [
447
- { proportion: 0.2, count: 2, totalCount: 11 },
448
- { proportion: 0.3, count: 3, totalCount: 12 },
455
+ { type: 'value', proportion: 0.2, count: 2, totalCount: 11 },
456
+ { type: 'value', proportion: 0.3, count: 3, totalCount: 12 },
449
457
  ],
450
458
  ]);
451
459
 
@@ -536,8 +544,8 @@ describe('queryMutationsOverTime', () => {
536
544
 
537
545
  expect(mutationOverTimeData.getAsArray()).to.deep.equal([
538
546
  [
539
- { proportion: 0.1, count: 1, totalCount: 11 },
540
- { proportion: 0.2, count: 2, totalCount: 12 },
547
+ { type: 'value', proportion: 0.1, count: 1, totalCount: 11 },
548
+ { type: 'value', proportion: 0.2, count: 2, totalCount: 12 },
541
549
  ],
542
550
  ]);
543
551
 
@@ -598,13 +606,7 @@ describe('queryMutationsOverTime', () => {
598
606
  });
599
607
 
600
608
  expect(mutationOverTimeData.getAsArray()).to.deep.equal([
601
- [
602
- {
603
- proportion: 0.2,
604
- count: 2,
605
- totalCount: 11,
606
- },
607
- ],
609
+ [{ type: 'value', proportion: 0.2, count: 2, totalCount: 11 }],
608
610
  ]);
609
611
 
610
612
  const sequences = mutationOverTimeData.getFirstAxisKeys();
@@ -693,12 +695,12 @@ describe('queryMutationsOverTime', () => {
693
695
 
694
696
  expect(mutationOverTimeData.getAsArray()).to.deep.equal([
695
697
  [
696
- { proportion: 0.4, count: 4, totalCount: 11 },
697
- { proportion: 0, count: 0, totalCount: 12 },
698
+ { type: 'value', proportion: 0.4, count: 4, totalCount: 11 },
699
+ { type: 'belowThreshold', totalCount: 12 },
698
700
  ],
699
701
  [
700
- { proportion: 0.1, count: 1, totalCount: 11 },
701
- { proportion: 0.2, count: 2, totalCount: 12 },
702
+ { type: 'value', proportion: 0.1, count: 1, totalCount: 11 },
703
+ { type: 'value', proportion: 0.2, count: 2, totalCount: 12 },
702
704
  ],
703
705
  ]);
704
706
 
@@ -741,7 +743,7 @@ describe('queryMutationsOverTime', () => {
741
743
  expect(dates.length).toBe(0);
742
744
  });
743
745
 
744
- it('should fill with 0 if the mutation does not exist in a date range but count > 0', async () => {
746
+ it('should fill with "belowThreshold" if the mutation does not exist in a date range but count > 0', async () => {
745
747
  const lapisFilter = { field1: 'value1', field2: 'value2' };
746
748
  const dateField = 'dateField';
747
749
 
@@ -820,8 +822,8 @@ describe('queryMutationsOverTime', () => {
820
822
 
821
823
  expect(mutationOverTimeData.getAsArray()).to.deep.equal([
822
824
  [
823
- { proportion: 0.1, count: 1, totalCount: 11 },
824
- { proportion: 0, count: 0, totalCount: 11 },
825
+ { type: 'value', proportion: 0.1, count: 1, totalCount: 11 },
826
+ { type: 'belowThreshold', totalCount: 11 },
825
827
  ],
826
828
  ]);
827
829
  });
@@ -34,13 +34,25 @@ export type MutationOverTimeData = {
34
34
  totalCount: number;
35
35
  };
36
36
 
37
- export type MutationOverTimeMutationValue = {
38
- proportion: number;
39
- count: number | null;
40
- totalCount: number | null;
41
- } | null;
37
+ export type MutationOverTimeMutationValue =
38
+ | {
39
+ type: 'value';
40
+ proportion: number;
41
+ count: number;
42
+ totalCount: number;
43
+ }
44
+ | {
45
+ type: 'wastewaterValue';
46
+ proportion: number;
47
+ }
48
+ | {
49
+ type: 'belowThreshold';
50
+ totalCount: number | null;
51
+ }
52
+ | null;
42
53
 
43
54
  const MAX_NUMBER_OF_GRID_COLUMNS = 200;
55
+ export const MUTATIONS_OVER_TIME_MIN_PROPORTION = 0.001;
44
56
 
45
57
  export async function queryOverallMutationData({
46
58
  lapisFilter,
@@ -204,7 +216,7 @@ function fetchAndPrepareDates<LapisDateField extends string>(
204
216
  }
205
217
 
206
218
  function fetchAndPrepareSubstitutionsOrDeletions(filter: LapisFilter, sequenceType: SequenceType) {
207
- return new FetchSubstitutionsOrDeletionsOperator(filter, sequenceType, 0.001);
219
+ return new FetchSubstitutionsOrDeletionsOperator(filter, sequenceType, MUTATIONS_OVER_TIME_MIN_PROPORTION);
208
220
  }
209
221
 
210
222
  export function serializeSubstitutionOrDeletion(mutation: Substitution | Deletion) {
@@ -248,6 +260,7 @@ export function groupByMutation(
248
260
 
249
261
  if (dataArray.get(mutation, date) !== undefined) {
250
262
  dataArray.set(mutation, date, {
263
+ type: 'value',
251
264
  count: mutationEntry.count,
252
265
  proportion: mutationEntry.proportion,
253
266
  totalCount: mutationData.totalCount,
@@ -258,8 +271,7 @@ export function groupByMutation(
258
271
  for (const firstAxisKey of dataArray.getFirstAxisKeys()) {
259
272
  if (dataArray.get(firstAxisKey, date) === null) {
260
273
  dataArray.set(firstAxisKey, date, {
261
- count: 0,
262
- proportion: 0,
274
+ type: 'belowThreshold',
263
275
  totalCount: mutationData.totalCount,
264
276
  });
265
277
  }
@@ -98,7 +98,7 @@ export const TestRenderAttributesInHtmlInsteadOfUsingPropertyExpression: StoryOb
98
98
  <gs-date-range-filter
99
99
  .dateRangeOptions=${args.dateRangeOptions}
100
100
  earliestDate="${args.earliestDate}"
101
- value="${args.value}"
101
+ value="${args.value ?? 'null'}"
102
102
  width="${args.width}"
103
103
  lapisDateField="${args.lapisDateField}"
104
104
  placeholder="${args.placeholder}"
@@ -38,12 +38,14 @@ import { PreactLitAdapter } from '../PreactLitAdapter';
38
38
  * Use this event, when you want to use the filter directly as a LAPIS filter.
39
39
  *
40
40
  *
41
- * @fires {CustomEvent<{ string | {dateFrom: string, dateTo: string}}>} gs-date-range-option-changed
41
+ * @fires {CustomEvent<string | {dateFrom: string, dateTo: string} | null>} gs-date-range-option-changed
42
42
  * Fired when:
43
43
  * - The select field is changed,
44
44
  * - A date is selected in either of the date pickers,
45
- * - A date was typed into either of the date input fields, and the input field loses focus ("on blur").
46
- * Contains the selected dateRangeOption or when users select custom values it contains the selected dates.
45
+ * - A date was typed into either of the date input fields, and the input field loses focus ("on blur"),
46
+ * - The user deletes the current value by clicking the 'x' button.
47
+ * Contains the selected dateRangeOption or when users select custom values it contains the selected dates
48
+ * or `null` when the input was deleted.
47
49
  *
48
50
  * Use this event, when you want to control this component in your JS application.
49
51
  * You can supply the `detail` of this event to the `value` attribute of this component.
@@ -80,7 +82,7 @@ export class DateRangeFilterComponent extends PreactLitAdapter {
80
82
  @property({
81
83
  converter: (value) => {
82
84
  if (value === null) {
83
- return undefined;
85
+ return null;
84
86
  }
85
87
  try {
86
88
  const result = JSON.parse(value) as unknown;
@@ -93,7 +95,7 @@ export class DateRangeFilterComponent extends PreactLitAdapter {
93
95
  }
94
96
  },
95
97
  })
96
- value: string | { dateFrom?: string; dateTo?: string } | undefined = undefined;
98
+ value: string | { dateFrom?: string; dateTo?: string } | null = null;
97
99
 
98
100
  /**
99
101
  * The width of the component.