@genspectrum/dashboard-components 0.18.4 → 0.18.6

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 (37) hide show
  1. package/README.md +12 -0
  2. package/custom-elements.json +1 -1
  3. package/dist/components.d.ts +44 -44
  4. package/dist/components.js +826 -343
  5. package/dist/components.js.map +1 -1
  6. package/dist/style.css +2 -2
  7. package/dist/util.d.ts +44 -44
  8. package/package.json +2 -2
  9. package/src/preact/MutationAnnotationsContext.tsx +34 -27
  10. package/src/preact/components/dropdown.tsx +1 -1
  11. package/src/preact/components/info.tsx +1 -1
  12. package/src/preact/components/mutations-over-time-text-filter.stories.tsx +57 -0
  13. package/src/preact/components/mutations-over-time-text-filter.tsx +63 -0
  14. package/src/preact/components/segment-selector.stories.tsx +12 -5
  15. package/src/preact/components/segment-selector.tsx +11 -7
  16. package/src/preact/mutationComparison/mutation-comparison.tsx +5 -1
  17. package/src/preact/mutationFilter/mutation-filter.stories.tsx +169 -50
  18. package/src/preact/mutationFilter/mutation-filter.tsx +239 -234
  19. package/src/preact/mutationFilter/parseAndValidateMutation.ts +62 -10
  20. package/src/preact/mutationFilter/parseMutation.spec.ts +62 -47
  21. package/src/preact/mutations/mutations.tsx +5 -1
  22. package/src/preact/mutationsOverTime/getFilteredMutationsOverTime.spec.ts +128 -0
  23. package/src/preact/mutationsOverTime/getFilteredMutationsOverTimeData.ts +39 -2
  24. package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +9 -12
  25. package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +27 -0
  26. package/src/preact/mutationsOverTime/mutations-over-time.tsx +31 -6
  27. package/src/preact/sequencesByLocation/__mockData__/worldAtlas.json +1 -1
  28. package/src/preact/shared/tanstackTable/pagination-context.tsx +30 -0
  29. package/src/preact/shared/tanstackTable/pagination.tsx +41 -21
  30. package/src/preact/shared/tanstackTable/tanstackTable.tsx +17 -3
  31. package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.stories.tsx +22 -4
  32. package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.tsx +11 -2
  33. package/src/web-components/input/gs-mutation-filter.stories.ts +4 -4
  34. package/src/web-components/visualization/gs-prevalence-over-time.stories.ts +1 -1
  35. package/standalone-bundle/dashboard-components.js +12896 -13334
  36. package/standalone-bundle/dashboard-components.js.map +1 -1
  37. package/standalone-bundle/style.css +1 -1
@@ -27,135 +27,150 @@ describe('parseMutation', () => {
27
27
  insertions: [
28
28
  {
29
29
  name: 'should parse nucleotide insertions',
30
- input: 'ins_10:ACGT',
31
- expected: { type: 'nucleotideInsertions', value: new InsertionClass(undefined, 10, 'ACGT') },
30
+ input: 'ins_3:ACGT',
31
+ expected: { type: 'nucleotideInsertions', value: new InsertionClass(undefined, 3, 'ACGT') },
32
32
  },
33
33
  {
34
34
  name: 'should parse amino acid insertions',
35
- input: 'ins_gene1:10:ACGT',
36
- expected: { type: 'aminoAcidInsertions', value: new InsertionClass('gene1', 10, 'ACGT') },
35
+ input: 'ins_gene1:3:ACGT',
36
+ expected: { type: 'aminoAcidInsertions', value: new InsertionClass('gene1', 3, 'ACGT') },
37
37
  },
38
38
  {
39
39
  name: 'should parse amino acid insertions in all upper case',
40
- input: 'INS_GENE1:10:ACGT',
41
- expected: { type: 'aminoAcidInsertions', value: new InsertionClass('GENE1', 10, 'ACGT') },
40
+ input: 'INS_GENE1:3:ACGT',
41
+ expected: { type: 'aminoAcidInsertions', value: new InsertionClass('GENE1', 3, 'ACGT') },
42
42
  },
43
43
  {
44
44
  name: 'should parse amino acid insertions in all lower case',
45
- input: 'ins_gene1:10:acgt',
46
- expected: { type: 'aminoAcidInsertions', value: new InsertionClass('gene1', 10, 'acgt') },
45
+ input: 'ins_gene1:3:acgt',
46
+ expected: { type: 'aminoAcidInsertions', value: new InsertionClass('gene1', 3, 'acgt') },
47
47
  },
48
48
  {
49
49
  name: 'should parse amino acid insertion with LAPIS-style wildcard',
50
- input: 'ins_gene1:10:?AC?GT',
51
- expected: { type: 'aminoAcidInsertions', value: new InsertionClass('gene1', 10, '?AC?GT') },
50
+ input: 'ins_gene1:3:?AC?GT',
51
+ expected: { type: 'aminoAcidInsertions', value: new InsertionClass('gene1', 3, '?AC?GT') },
52
52
  },
53
53
  {
54
54
  name: 'should parse amino acid insertion with SILO-style wildcard',
55
- input: 'ins_gene1:10:.*AC.*GT',
56
- expected: { type: 'aminoAcidInsertions', value: new InsertionClass('gene1', 10, '.*AC.*GT') },
55
+ input: 'ins_gene1:3:.*AC.*GT',
56
+ expected: { type: 'aminoAcidInsertions', value: new InsertionClass('gene1', 3, '.*AC.*GT') },
57
57
  },
58
58
  {
59
59
  name: 'should return null for insertion with segment not in reference genome',
60
- input: 'INS_notInReferenceGenome:10:ACGT',
60
+ input: 'INS_notInReferenceGenome:3:ACGT',
61
61
  expected: null,
62
62
  },
63
63
  { name: 'should return null for insertion with missing position', input: 'ins_gene1:ACGT', expected: null },
64
+ {
65
+ name: 'should return null for insertion with position outside of reference genome',
66
+ input: 'ins_gene1:10:ACGT',
67
+ expected: null,
68
+ },
64
69
  ],
65
70
  deletions: [
66
71
  {
67
72
  name: 'should parse nucleotide deletion in single segmented reference genome, when no segment is given',
68
- input: 'A123-',
69
- expected: { type: 'nucleotideMutations', value: new DeletionClass(undefined, 'A', 123) },
73
+ input: 'A3-',
74
+ expected: { type: 'nucleotideMutations', value: new DeletionClass(undefined, 'A', 3) },
70
75
  },
71
76
  {
72
77
  name: 'should parse nucleotide deletion without valueAtReference when no segment is given',
73
- input: '123-',
74
- expected: { type: 'nucleotideMutations', value: new DeletionClass(undefined, undefined, 123) },
78
+ input: '3-',
79
+ expected: { type: 'nucleotideMutations', value: new DeletionClass(undefined, undefined, 3) },
75
80
  },
76
81
  {
77
82
  name: 'should parse nucleotide deletion',
78
- input: 'nuc1:A123-',
79
- expected: { type: 'nucleotideMutations', value: new DeletionClass('nuc1', 'A', 123) },
83
+ input: 'nuc1:A3-',
84
+ expected: { type: 'nucleotideMutations', value: new DeletionClass('nuc1', 'A', 3) },
80
85
  },
81
86
  {
82
87
  name: 'should parse nucleotide deletion without valueAtReference',
83
- input: 'nuc1:123-',
84
- expected: { type: 'nucleotideMutations', value: new DeletionClass('nuc1', undefined, 123) },
88
+ input: 'nuc1:3-',
89
+ expected: { type: 'nucleotideMutations', value: new DeletionClass('nuc1', undefined, 3) },
85
90
  },
86
91
  {
87
92
  name: 'should parse amino acid deletion',
88
- input: 'gene1:A123-',
89
- expected: { type: 'aminoAcidMutations', value: new DeletionClass('gene1', 'A', 123) },
93
+ input: 'gene1:A3-',
94
+ expected: { type: 'aminoAcidMutations', value: new DeletionClass('gene1', 'A', 3) },
90
95
  },
91
96
  {
92
97
  name: 'should parse amino acid deletion in all upper case',
93
- input: 'GENE1:A123-',
94
- expected: { type: 'aminoAcidMutations', value: new DeletionClass('GENE1', 'A', 123) },
98
+ input: 'GENE1:A3-',
99
+ expected: { type: 'aminoAcidMutations', value: new DeletionClass('GENE1', 'A', 3) },
95
100
  },
96
101
  {
97
102
  name: 'should parse amino acid deletion in all lower case',
98
- input: 'gene1:a123-',
99
- expected: { type: 'aminoAcidMutations', value: new DeletionClass('gene1', 'a', 123) },
103
+ input: 'gene1:a3-',
104
+ expected: { type: 'aminoAcidMutations', value: new DeletionClass('gene1', 'a', 3) },
100
105
  },
101
106
  {
102
107
  name: 'should parse amino acid deletion without valueAtReference',
103
- input: 'gene1:123-',
104
- expected: { type: 'aminoAcidMutations', value: new DeletionClass('gene1', undefined, 123) },
108
+ input: 'gene1:3-',
109
+ expected: { type: 'aminoAcidMutations', value: new DeletionClass('gene1', undefined, 3) },
105
110
  },
106
111
  {
107
112
  name: 'should return null for deletion with segment not in reference genome',
108
- input: 'notInReferenceGenome:A123-',
113
+ input: 'notInReferenceGenome:A3-',
114
+ expected: null,
115
+ },
116
+ {
117
+ name: 'should return null for deletion with position outside reference genome',
118
+ input: 'gene1:A10-',
109
119
  expected: null,
110
120
  },
111
121
  ],
112
122
  substitutions: [
113
123
  {
114
124
  name: 'should parse nucleotide substitution in single segmented reference genome, when no segment is given',
115
- input: 'A123T',
116
- expected: { type: 'nucleotideMutations', value: new SubstitutionClass(undefined, 'A', 'T', 123) },
125
+ input: 'A3T',
126
+ expected: { type: 'nucleotideMutations', value: new SubstitutionClass(undefined, 'A', 'T', 3) },
117
127
  },
118
128
  {
119
129
  name: 'should parse substitution without valueAtReference',
120
- input: '123T',
121
- expected: { type: 'nucleotideMutations', value: new SubstitutionClass(undefined, undefined, 'T', 123) },
130
+ input: '3T',
131
+ expected: { type: 'nucleotideMutations', value: new SubstitutionClass(undefined, undefined, 'T', 3) },
122
132
  },
123
133
  {
124
134
  name: 'should parse substitution with neither valueAtReference not substitutionValue',
125
- input: '123',
135
+ input: '3',
126
136
  expected: {
127
137
  type: 'nucleotideMutations',
128
- value: new SubstitutionClass(undefined, undefined, undefined, 123),
138
+ value: new SubstitutionClass(undefined, undefined, undefined, 3),
129
139
  },
130
140
  },
131
141
  {
132
142
  name: 'should parse a "no mutation" substitution',
133
- input: '123.',
134
- expected: { type: 'nucleotideMutations', value: new SubstitutionClass(undefined, undefined, '.', 123) },
143
+ input: '3.',
144
+ expected: { type: 'nucleotideMutations', value: new SubstitutionClass(undefined, undefined, '.', 3) },
135
145
  },
136
146
  {
137
147
  name: 'should parse nucleotide substitution',
138
- input: 'nuc1:A123T',
139
- expected: { type: 'nucleotideMutations', value: new SubstitutionClass('nuc1', 'A', 'T', 123) },
148
+ input: 'nuc1:A3T',
149
+ expected: { type: 'nucleotideMutations', value: new SubstitutionClass('nuc1', 'A', 'T', 3) },
140
150
  },
141
151
  {
142
152
  name: 'should parse amino acid substitution',
143
- input: 'gene1:A123T',
144
- expected: { type: 'aminoAcidMutations', value: new SubstitutionClass('gene1', 'A', 'T', 123) },
153
+ input: 'gene1:A3T',
154
+ expected: { type: 'aminoAcidMutations', value: new SubstitutionClass('gene1', 'A', 'T', 3) },
145
155
  },
146
156
  {
147
157
  name: 'should parse amino acid substitution in all upper case',
148
- input: 'GENE1:A123T',
149
- expected: { type: 'aminoAcidMutations', value: new SubstitutionClass('GENE1', 'A', 'T', 123) },
158
+ input: 'GENE1:A3T',
159
+ expected: { type: 'aminoAcidMutations', value: new SubstitutionClass('GENE1', 'A', 'T', 3) },
150
160
  },
151
161
  {
152
162
  name: 'should parse amino acid substitution in all lower case',
153
- input: 'gene1:a123t',
154
- expected: { type: 'aminoAcidMutations', value: new SubstitutionClass('gene1', 'a', 't', 123) },
163
+ input: 'gene1:a3t',
164
+ expected: { type: 'aminoAcidMutations', value: new SubstitutionClass('gene1', 'a', 't', 3) },
155
165
  },
156
166
  {
157
167
  name: 'should return null for substitution with segment not in reference genome',
158
- input: 'notInReferenceGenome:A123T',
168
+ input: 'notInReferenceGenome:A3T',
169
+ expected: null,
170
+ },
171
+ {
172
+ name: 'should return null for substitution with position outside reference genome',
173
+ input: 'gene1:A10T',
159
174
  expected: null,
160
175
  },
161
176
  ],
@@ -185,7 +185,11 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
185
185
  }) => {
186
186
  return (
187
187
  <>
188
- <SegmentSelector displayedSegments={displayedSegments} setDisplayedSegments={setDisplayedSegments} />
188
+ <SegmentSelector
189
+ displayedSegments={displayedSegments}
190
+ setDisplayedSegments={setDisplayedSegments}
191
+ sequenceType={originalComponentProps.sequenceType}
192
+ />
189
193
  {activeTab === 'Table' && (
190
194
  <MutationTypeSelector
191
195
  setDisplayedMutationTypes={setDisplayedMutationTypes}
@@ -7,6 +7,8 @@ import { type DeletionEntry, type SubstitutionEntry } from '../../types';
7
7
  import { type Deletion, type Substitution } from '../../utils/mutations';
8
8
  import { type TemporalClass } from '../../utils/temporalClass';
9
9
  import { yearMonthDay } from '../../utils/temporalTestHelpers';
10
+ import { type MutationAnnotations } from '../../web-components/mutation-annotations-context';
11
+ import { getMutationAnnotationsContext, getMutationAnnotationsProvider } from '../MutationAnnotationsContext';
10
12
 
11
13
  describe('getFilteredMutationOverTimeData', () => {
12
14
  it('should filter by displayed segments', () => {
@@ -26,6 +28,11 @@ describe('getFilteredMutationOverTimeData', () => {
26
28
  displayedMutationTypes: [],
27
29
  proportionInterval,
28
30
  displayMutations: undefined,
31
+ mutationFilterValue: '',
32
+ sequenceType: 'nucleotide',
33
+ annotationProvider: () => {
34
+ return [];
35
+ },
29
36
  });
30
37
 
31
38
  expect(result.getFirstAxisKeys()).to.deep.equal([anotherSubstitution]);
@@ -55,6 +62,11 @@ describe('getFilteredMutationOverTimeData', () => {
55
62
  },
56
63
  ],
57
64
  proportionInterval,
65
+ mutationFilterValue: '',
66
+ sequenceType: 'nucleotide',
67
+ annotationProvider: () => {
68
+ return [];
69
+ },
58
70
  });
59
71
 
60
72
  expect(result.getFirstAxisKeys()).to.deep.equal([someDeletion]);
@@ -73,6 +85,11 @@ describe('getFilteredMutationOverTimeData', () => {
73
85
  displayedSegments: [],
74
86
  displayedMutationTypes: [],
75
87
  proportionInterval,
88
+ mutationFilterValue: '',
89
+ sequenceType: 'nucleotide',
90
+ annotationProvider: () => {
91
+ return [];
92
+ },
76
93
  });
77
94
 
78
95
  expect(result.getFirstAxisKeys()).to.deep.equal([anotherSubstitution, someDeletion]);
@@ -91,6 +108,11 @@ describe('getFilteredMutationOverTimeData', () => {
91
108
  displayedSegments: [],
92
109
  displayedMutationTypes: [],
93
110
  proportionInterval,
111
+ mutationFilterValue: '',
112
+ sequenceType: 'nucleotide',
113
+ annotationProvider: () => {
114
+ return [];
115
+ },
94
116
  });
95
117
 
96
118
  expect(result.getFirstAxisKeys()).to.deep.equal([anotherSubstitution, someDeletion]);
@@ -110,6 +132,11 @@ describe('getFilteredMutationOverTimeData', () => {
110
132
  displayedSegments: [],
111
133
  displayedMutationTypes: [],
112
134
  proportionInterval,
135
+ mutationFilterValue: '',
136
+ sequenceType: 'nucleotide',
137
+ annotationProvider: () => {
138
+ return [];
139
+ },
113
140
  });
114
141
 
115
142
  expect(result.getFirstAxisKeys()).to.deep.equal([someSubstitution, anotherSubstitution, someDeletion]);
@@ -129,6 +156,11 @@ describe('getFilteredMutationOverTimeData', () => {
129
156
  displayedSegments: [],
130
157
  displayedMutationTypes: [],
131
158
  proportionInterval,
159
+ mutationFilterValue: '',
160
+ sequenceType: 'nucleotide',
161
+ annotationProvider: () => {
162
+ return [];
163
+ },
132
164
  });
133
165
 
134
166
  expect(result.getFirstAxisKeys()).to.deep.equal([someSubstitution, anotherSubstitution, someDeletion]);
@@ -146,6 +178,11 @@ describe('getFilteredMutationOverTimeData', () => {
146
178
  displayedSegments: [],
147
179
  displayedMutationTypes: [],
148
180
  proportionInterval,
181
+ mutationFilterValue: '',
182
+ sequenceType: 'nucleotide',
183
+ annotationProvider: () => {
184
+ return [];
185
+ },
149
186
  });
150
187
 
151
188
  expect(result.getFirstAxisKeys()).to.deep.equal([someSubstitution, anotherSubstitution, someDeletion]);
@@ -164,6 +201,11 @@ describe('getFilteredMutationOverTimeData', () => {
164
201
  displayedSegments: [],
165
202
  displayedMutationTypes: [],
166
203
  proportionInterval,
204
+ mutationFilterValue: '',
205
+ sequenceType: 'nucleotide',
206
+ annotationProvider: () => {
207
+ return [];
208
+ },
167
209
  });
168
210
 
169
211
  expect(result.getFirstAxisKeys()).to.deep.equal([someSubstitution, anotherSubstitution, someDeletion]);
@@ -183,11 +225,97 @@ describe('getFilteredMutationOverTimeData', () => {
183
225
  displayedMutationTypes: [],
184
226
  proportionInterval,
185
227
  displayMutations: [anotherSubstitution.code, someDeletion.code],
228
+ mutationFilterValue: '',
229
+ sequenceType: 'nucleotide',
230
+ annotationProvider: () => {
231
+ return [];
232
+ },
186
233
  });
187
234
 
188
235
  expect(result.getFirstAxisKeys()).to.deep.equal([anotherSubstitution, someDeletion]);
189
236
  });
190
237
 
238
+ it('should filter by mutation filter value', () => {
239
+ const { data, overallMutationData } = prepareMutationOverTimeData([
240
+ someSubstitutionEntry,
241
+ anotherSubstitutionEntry,
242
+ someDeletionEntry,
243
+ ]);
244
+
245
+ const result = getFilteredMutationOverTimeData({
246
+ data,
247
+ overallMutationData,
248
+ displayedSegments: [],
249
+ displayedMutationTypes: [],
250
+ proportionInterval,
251
+ mutationFilterValue: '23T',
252
+ sequenceType: 'nucleotide',
253
+ annotationProvider: () => {
254
+ return [];
255
+ },
256
+ });
257
+
258
+ expect(result.getFirstAxisKeys()).to.deep.equal([someSubstitution]);
259
+ });
260
+
261
+ describe('should filter by annotation', () => {
262
+ const { data, overallMutationData } = prepareMutationOverTimeData([
263
+ someSubstitutionEntry,
264
+ anotherSubstitutionEntry,
265
+ someDeletionEntry,
266
+ ]);
267
+
268
+ const expectFilteredValue = (filterValue: string, annotations: MutationAnnotations) => {
269
+ const annotationProvider = getMutationAnnotationsProvider(getMutationAnnotationsContext(annotations));
270
+
271
+ const result = getFilteredMutationOverTimeData({
272
+ data,
273
+ overallMutationData,
274
+ displayedSegments: [],
275
+ displayedMutationTypes: [],
276
+ proportionInterval,
277
+ mutationFilterValue: filterValue,
278
+ sequenceType: 'nucleotide',
279
+ annotationProvider,
280
+ });
281
+
282
+ expect(result.getFirstAxisKeys()).to.deep.equal([someSubstitution]);
283
+ };
284
+
285
+ it('with filter value in symbol', () => {
286
+ expectFilteredValue('#', [
287
+ {
288
+ name: 'Annotation 1',
289
+ description: 'Description 1',
290
+ symbol: '#',
291
+ nucleotideMutations: ['A123T'],
292
+ },
293
+ ]);
294
+ });
295
+
296
+ it('with filter value in name', () => {
297
+ expectFilteredValue('Annota', [
298
+ {
299
+ name: 'Annotation 1 #',
300
+ description: 'Description 1',
301
+ symbol: '+',
302
+ nucleotideMutations: ['A123T'],
303
+ },
304
+ ]);
305
+ });
306
+
307
+ it('with filter value in name', () => {
308
+ expectFilteredValue('Descr', [
309
+ {
310
+ name: 'Annotation 1',
311
+ description: 'Description 1',
312
+ symbol: '#',
313
+ nucleotideMutations: ['A123T'],
314
+ },
315
+ ]);
316
+ });
317
+ });
318
+
191
319
  const belowFilter = 0.1;
192
320
  const atFilterMin = 0.2;
193
321
  const inFilter = 0.5;
@@ -1,9 +1,10 @@
1
1
  import z from 'zod';
2
2
 
3
3
  import { type MutationOverTimeDataMap } from './MutationOverTimeData';
4
- import { type SubstitutionOrDeletionEntry } from '../../types';
4
+ import { type SequenceType, type SubstitutionOrDeletionEntry } from '../../types';
5
5
  import { Map2dView } from '../../utils/map2d';
6
- import type { Deletion, Substitution } from '../../utils/mutations';
6
+ import type { Deletion, Mutation, Substitution } from '../../utils/mutations';
7
+ import { type useMutationAnnotationsProvider } from '../MutationAnnotationsContext';
7
8
  import type { DisplayedMutationType } from '../components/mutation-type-selector';
8
9
  import type { DisplayedSegment } from '../components/segment-selector';
9
10
 
@@ -17,6 +18,9 @@ export type GetFilteredMutationOverTimeDataArgs = {
17
18
  displayedMutationTypes: DisplayedMutationType[];
18
19
  proportionInterval: { min: number; max: number };
19
20
  displayMutations?: DisplayMutations;
21
+ mutationFilterValue: string;
22
+ sequenceType: SequenceType;
23
+ annotationProvider: ReturnType<typeof useMutationAnnotationsProvider>;
20
24
  };
21
25
 
22
26
  export function getFilteredMutationOverTimeData({
@@ -26,6 +30,9 @@ export function getFilteredMutationOverTimeData({
26
30
  displayedMutationTypes,
27
31
  proportionInterval,
28
32
  displayMutations,
33
+ mutationFilterValue,
34
+ sequenceType,
35
+ annotationProvider,
29
36
  }: GetFilteredMutationOverTimeDataArgs) {
30
37
  const filteredData = new Map2dView(data);
31
38
 
@@ -44,6 +51,10 @@ export function getFilteredMutationOverTimeData({
44
51
  return true;
45
52
  }
46
53
 
54
+ if (applySearchFilter(entry.mutation, sequenceType, mutationFilterValue, annotationProvider)) {
55
+ return true;
56
+ }
57
+
47
58
  return displayedMutationTypes.some(
48
59
  (mutationType) => mutationType.type === entry.mutation.type && !mutationType.checked,
49
60
  );
@@ -55,3 +66,29 @@ export function getFilteredMutationOverTimeData({
55
66
 
56
67
  return filteredData;
57
68
  }
69
+
70
+ export function applySearchFilter(
71
+ mutation: Mutation,
72
+ sequenceType: SequenceType,
73
+ filterValue: string,
74
+ annotationProvider: ReturnType<typeof useMutationAnnotationsProvider>,
75
+ ) {
76
+ if (filterValue === '') {
77
+ return false;
78
+ }
79
+
80
+ if (mutation.code.includes(filterValue)) {
81
+ return false;
82
+ }
83
+
84
+ const mutationAnnotations = annotationProvider(mutation, sequenceType);
85
+ if (mutationAnnotations === undefined || mutationAnnotations.length === 0) {
86
+ return true;
87
+ }
88
+ return !mutationAnnotations.some(
89
+ (annotation) =>
90
+ annotation.description.includes(filterValue) ||
91
+ annotation.name.includes(filterValue) ||
92
+ annotation.symbol.includes(filterValue),
93
+ );
94
+ }
@@ -1,6 +1,5 @@
1
- import { type PaginationState } from '@tanstack/table-core';
2
1
  import { type FunctionComponent } from 'preact';
3
- import { useMemo, useState } from 'preact/hooks';
2
+ import { useMemo } from 'preact/hooks';
4
3
 
5
4
  import { type MutationOverTimeDataMap } from './MutationOverTimeData';
6
5
  import { MutationsOverTimeGridTooltip } from './mutations-over-time-grid-tooltip';
@@ -13,6 +12,7 @@ import { type ColorScale, getColorWithinScale, getTextColorForScale } from '../c
13
12
  import Tooltip, { type TooltipPosition } from '../components/tooltip';
14
13
  import { formatProportion } from '../shared/table/formatProportion';
15
14
  import { type PageSizes, Pagination } from '../shared/tanstackTable/pagination';
15
+ import { usePageSizeContext } from '../shared/tanstackTable/pagination-context';
16
16
  import {
17
17
  createColumnHelper,
18
18
  flexRender,
@@ -43,11 +43,6 @@ const MutationsOverTimeGrid: FunctionComponent<MutationsOverTimeGridProps> = ({
43
43
  });
44
44
  }, [data]);
45
45
 
46
- const [pagination, setPagination] = useState<PaginationState>({
47
- pageIndex: 0,
48
- pageSize: typeof pageSizes === 'number' ? pageSizes : (pageSizes.at(0) ?? 10),
49
- });
50
-
51
46
  const columns = useMemo(() => {
52
47
  const columnHelper = createColumnHelper<RowType>();
53
48
  const dates = data.getSecondAxisKeys();
@@ -104,15 +99,17 @@ const MutationsOverTimeGrid: FunctionComponent<MutationsOverTimeGridProps> = ({
104
99
  return [mutationHeader, ...dateHeaders];
105
100
  }, [colorScale, data, sequenceType]);
106
101
 
102
+ const { pageSize } = usePageSizeContext();
107
103
  const table = usePreactTable({
108
104
  data: tableData,
109
105
  columns,
110
106
  getCoreRowModel: getCoreRowModel(),
111
107
  getPaginationRowModel: getPaginationRowModel(),
112
- debugTable: true,
113
- onPaginationChange: setPagination,
114
- state: {
115
- pagination,
108
+ initialState: {
109
+ pagination: {
110
+ pageIndex: 0,
111
+ pageSize,
112
+ },
116
113
  },
117
114
  });
118
115
 
@@ -192,7 +189,7 @@ const ProportionCell: FunctionComponent<{
192
189
  backgroundColor: getColorWithinScale(proportion, colorScale),
193
190
  color: getTextColorForScale(proportion, colorScale),
194
191
  }}
195
- className={`w-full h-full hover:font-bold text-xs group @container`}
192
+ className={`w-full h-full hover:font-bold text-xs group @container text-nowrap`}
196
193
  >
197
194
  {value === null ? (
198
195
  <span className='invisible'>No data</span>
@@ -81,6 +81,10 @@ export const Default: StoryObj<MutationsOverTimeProps> = {
81
81
  initialMeanProportionInterval: { min: 0.05, max: 0.9 },
82
82
  pageSizes: [10, 20, 30, 40, 50],
83
83
  },
84
+ };
85
+
86
+ export const ShowsMutationAnnotations: StoryObj<MutationsOverTimeProps> = {
87
+ ...Default,
84
88
  play: async ({ canvasElement }) => {
85
89
  await expectMutationAnnotation(canvasElement, 'C44T');
86
90
  },
@@ -133,6 +137,29 @@ export const UsesPagination: StoryObj<MutationsOverTimeProps> = {
133
137
  },
134
138
  };
135
139
 
140
+ export const UsesMutationFilter: StoryObj<MutationsOverTimeProps> = {
141
+ ...Default,
142
+ play: async ({ canvas, step }) => {
143
+ await expectMutationOnPage(canvas, 'C44T');
144
+
145
+ await step('input filter', async () => {
146
+ const filterButton = canvas.getByRole('button', { name: 'Filter mutations' });
147
+ await userEvent.click(filterButton);
148
+
149
+ const filterInput = canvas.getByPlaceholderText('Filter');
150
+ await userEvent.type(filterInput, 'T21');
151
+ });
152
+
153
+ await step('should show only matching filter', async () => {
154
+ await expectMutationOnPage(canvas, 'T21653-');
155
+ await expectMutationOnPage(canvas, 'T21655-');
156
+
157
+ const filteredMutation = canvas.queryByText('C44T');
158
+ await expect(filteredMutation).not.toBeInTheDocument();
159
+ });
160
+ },
161
+ };
162
+
136
163
  async function expectMutationOnPage(canvas: Canvas, mutation: string) {
137
164
  await waitFor(async () => {
138
165
  const mutationOnFirstPage = canvas.getAllByText(mutation)[0];