@genspectrum/dashboard-components 0.18.5 → 0.19.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.
- package/README.md +12 -2
- package/custom-elements.json +3 -3
- package/dist/assets/{mutationOverTimeWorker--b8ZHlji.js.map → mutationOverTimeWorker-ChQTFL68.js.map} +1 -1
- package/dist/components.d.ts +15 -14
- package/dist/components.js +1602 -332
- package/dist/components.js.map +1 -1
- package/dist/util.d.ts +14 -14
- package/package.json +3 -4
- package/src/preact/MutationAnnotationsContext.tsx +34 -27
- package/src/preact/components/dropdown.tsx +1 -1
- package/src/preact/components/info.tsx +1 -2
- package/src/preact/components/min-max-range-slider.tsx +0 -2
- package/src/preact/components/mutations-over-time-text-filter.stories.tsx +57 -0
- package/src/preact/components/mutations-over-time-text-filter.tsx +63 -0
- package/src/preact/components/segment-selector.tsx +1 -1
- package/src/preact/components/table.tsx +0 -2
- package/src/preact/dateRangeFilter/date-picker.tsx +15 -10
- package/src/preact/mutationFilter/mutation-filter.stories.tsx +169 -50
- package/src/preact/mutationFilter/mutation-filter.tsx +239 -234
- package/src/preact/mutationFilter/parseAndValidateMutation.ts +62 -10
- package/src/preact/mutationFilter/parseMutation.spec.ts +62 -47
- package/src/preact/mutationsOverTime/getFilteredMutationsOverTime.spec.ts +128 -0
- package/src/preact/mutationsOverTime/getFilteredMutationsOverTimeData.ts +39 -2
- package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +8 -11
- package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +27 -0
- package/src/preact/mutationsOverTime/mutations-over-time.tsx +26 -5
- package/src/preact/shared/tanstackTable/pagination-context.tsx +30 -0
- package/src/preact/shared/tanstackTable/pagination.tsx +19 -6
- package/src/preact/shared/tanstackTable/tanstackTable.tsx +17 -3
- package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.stories.tsx +19 -1
- package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.tsx +6 -1
- package/src/styles/replaceCssProperties.stories.tsx +49 -0
- package/src/styles/replaceCssProperties.ts +25 -0
- package/src/styles/tailwind.css +1 -0
- package/src/web-components/PreactLitAdapter.tsx +6 -3
- package/src/web-components/PreactLitAdapterWithGridJsStyles.tsx +0 -2
- package/src/web-components/gs-app.stories.ts +6 -2
- package/src/web-components/gs-app.ts +4 -1
- package/src/web-components/input/gs-date-range-filter.tsx +6 -0
- package/src/web-components/input/gs-mutation-filter.stories.ts +4 -4
- package/src/web-components/visualization/gs-prevalence-over-time.stories.ts +1 -1
- package/standalone-bundle/assets/mutationOverTimeWorker-jChgWnwp.js.map +1 -1
- package/standalone-bundle/dashboard-components.js +10836 -11289
- package/standalone-bundle/dashboard-components.js.map +1 -1
- package/dist/style.css +0 -392
- package/standalone-bundle/style.css +0 -1
|
@@ -27,135 +27,150 @@ describe('parseMutation', () => {
|
|
|
27
27
|
insertions: [
|
|
28
28
|
{
|
|
29
29
|
name: 'should parse nucleotide insertions',
|
|
30
|
-
input: '
|
|
31
|
-
expected: { type: 'nucleotideInsertions', value: new InsertionClass(undefined,
|
|
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:
|
|
36
|
-
expected: { type: 'aminoAcidInsertions', value: new InsertionClass('gene1',
|
|
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:
|
|
41
|
-
expected: { type: 'aminoAcidInsertions', value: new InsertionClass('GENE1',
|
|
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:
|
|
46
|
-
expected: { type: 'aminoAcidInsertions', value: new InsertionClass('gene1',
|
|
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:
|
|
51
|
-
expected: { type: 'aminoAcidInsertions', value: new InsertionClass('gene1',
|
|
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:
|
|
56
|
-
expected: { type: 'aminoAcidInsertions', value: new InsertionClass('gene1',
|
|
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:
|
|
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: '
|
|
69
|
-
expected: { type: 'nucleotideMutations', value: new DeletionClass(undefined, 'A',
|
|
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: '
|
|
74
|
-
expected: { type: 'nucleotideMutations', value: new DeletionClass(undefined, undefined,
|
|
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:
|
|
79
|
-
expected: { type: 'nucleotideMutations', value: new DeletionClass('nuc1', 'A',
|
|
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:
|
|
84
|
-
expected: { type: 'nucleotideMutations', value: new DeletionClass('nuc1', undefined,
|
|
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:
|
|
89
|
-
expected: { type: 'aminoAcidMutations', value: new DeletionClass('gene1', 'A',
|
|
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:
|
|
94
|
-
expected: { type: 'aminoAcidMutations', value: new DeletionClass('GENE1', 'A',
|
|
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:
|
|
99
|
-
expected: { type: 'aminoAcidMutations', value: new DeletionClass('gene1', 'a',
|
|
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:
|
|
104
|
-
expected: { type: 'aminoAcidMutations', value: new DeletionClass('gene1', undefined,
|
|
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:
|
|
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: '
|
|
116
|
-
expected: { type: 'nucleotideMutations', value: new SubstitutionClass(undefined, 'A', 'T',
|
|
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: '
|
|
121
|
-
expected: { type: 'nucleotideMutations', value: new SubstitutionClass(undefined, undefined, 'T',
|
|
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: '
|
|
135
|
+
input: '3',
|
|
126
136
|
expected: {
|
|
127
137
|
type: 'nucleotideMutations',
|
|
128
|
-
value: new SubstitutionClass(undefined, undefined, undefined,
|
|
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: '
|
|
134
|
-
expected: { type: 'nucleotideMutations', value: new SubstitutionClass(undefined, undefined, '.',
|
|
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:
|
|
139
|
-
expected: { type: 'nucleotideMutations', value: new SubstitutionClass('nuc1', 'A', 'T',
|
|
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:
|
|
144
|
-
expected: { type: 'aminoAcidMutations', value: new SubstitutionClass('gene1', 'A', 'T',
|
|
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:
|
|
149
|
-
expected: { type: 'aminoAcidMutations', value: new SubstitutionClass('GENE1', 'A', 'T',
|
|
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:
|
|
154
|
-
expected: { type: 'aminoAcidMutations', value: new SubstitutionClass('gene1', 'a', 't',
|
|
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:
|
|
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
|
],
|
|
@@ -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
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
108
|
+
initialState: {
|
|
109
|
+
pagination: {
|
|
110
|
+
pageIndex: 0,
|
|
111
|
+
pageSize,
|
|
112
|
+
},
|
|
116
113
|
},
|
|
117
114
|
});
|
|
118
115
|
|
|
@@ -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];
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
import { type Deletion, type Substitution } from '../../utils/mutations';
|
|
20
20
|
import { toTemporalClass } from '../../utils/temporalClass';
|
|
21
21
|
import { useLapisUrl } from '../LapisUrlContext';
|
|
22
|
+
import { useMutationAnnotationsProvider } from '../MutationAnnotationsContext';
|
|
22
23
|
import { type ColorScale } from '../components/color-scale-selector';
|
|
23
24
|
import { ColorScaleSelectorDropdown } from '../components/color-scale-selector-dropdown';
|
|
24
25
|
import { CsvDownloadButton } from '../components/csv-download-button';
|
|
@@ -27,6 +28,7 @@ import { Fullscreen } from '../components/fullscreen';
|
|
|
27
28
|
import Info, { InfoComponentCode, InfoHeadline1, InfoParagraph } from '../components/info';
|
|
28
29
|
import { LoadingDisplay } from '../components/loading-display';
|
|
29
30
|
import { type DisplayedMutationType, MutationTypeSelector } from '../components/mutation-type-selector';
|
|
31
|
+
import { MutationsOverTimeTextFilter } from '../components/mutations-over-time-text-filter';
|
|
30
32
|
import { NoDataDisplay } from '../components/no-data-display';
|
|
31
33
|
import type { ProportionInterval } from '../components/proportion-selector';
|
|
32
34
|
import { ProportionSelectorDropdown } from '../components/proportion-selector-dropdown';
|
|
@@ -34,6 +36,7 @@ import { ResizeContainer } from '../components/resize-container';
|
|
|
34
36
|
import { type DisplayedSegment, SegmentSelector, useDisplayedSegments } from '../components/segment-selector';
|
|
35
37
|
import Tabs from '../components/tabs';
|
|
36
38
|
import { pageSizesSchema } from '../shared/tanstackTable/pagination';
|
|
39
|
+
import { PageSizeContextProvider } from '../shared/tanstackTable/pagination-context';
|
|
37
40
|
import { useWebWorker } from '../webWorkers/useWebWorker';
|
|
38
41
|
|
|
39
42
|
const mutationsOverTimeViewSchema = z.literal(views.grid);
|
|
@@ -123,6 +126,9 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
|
|
|
123
126
|
originalComponentProps,
|
|
124
127
|
overallMutationData,
|
|
125
128
|
}) => {
|
|
129
|
+
const [mutationFilterValue, setMutationFilterValue] = useState('');
|
|
130
|
+
const annotationProvider = useMutationAnnotationsProvider();
|
|
131
|
+
|
|
126
132
|
const [proportionInterval, setProportionInterval] = useState(originalComponentProps.initialMeanProportionInterval);
|
|
127
133
|
const [colorScale, setColorScale] = useState<ColorScale>({ min: 0, max: 1, color: 'indigo' });
|
|
128
134
|
|
|
@@ -132,8 +138,6 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
|
|
|
132
138
|
{ label: 'Deletions', checked: true, type: 'deletion' },
|
|
133
139
|
]);
|
|
134
140
|
|
|
135
|
-
const displayMutations = originalComponentProps.displayMutations;
|
|
136
|
-
|
|
137
141
|
const filteredData = useMemo(() => {
|
|
138
142
|
return getFilteredMutationOverTimeData({
|
|
139
143
|
data: mutationOverTimeData,
|
|
@@ -141,7 +145,10 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
|
|
|
141
145
|
displayedSegments,
|
|
142
146
|
displayedMutationTypes,
|
|
143
147
|
proportionInterval,
|
|
144
|
-
displayMutations,
|
|
148
|
+
displayMutations: originalComponentProps.displayMutations,
|
|
149
|
+
mutationFilterValue,
|
|
150
|
+
sequenceType: originalComponentProps.sequenceType,
|
|
151
|
+
annotationProvider,
|
|
145
152
|
});
|
|
146
153
|
}, [
|
|
147
154
|
mutationOverTimeData,
|
|
@@ -149,7 +156,10 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
|
|
|
149
156
|
displayedSegments,
|
|
150
157
|
displayedMutationTypes,
|
|
151
158
|
proportionInterval,
|
|
152
|
-
displayMutations,
|
|
159
|
+
originalComponentProps.displayMutations,
|
|
160
|
+
originalComponentProps.sequenceType,
|
|
161
|
+
mutationFilterValue,
|
|
162
|
+
annotationProvider,
|
|
153
163
|
]);
|
|
154
164
|
|
|
155
165
|
const getTab = (view: MutationsOverTimeView) => {
|
|
@@ -190,10 +200,16 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
|
|
|
190
200
|
colorScale={colorScale}
|
|
191
201
|
setColorScale={setColorScale}
|
|
192
202
|
originalComponentProps={originalComponentProps}
|
|
203
|
+
setFilterValue={setMutationFilterValue}
|
|
204
|
+
mutationFilterValue={mutationFilterValue}
|
|
193
205
|
/>
|
|
194
206
|
);
|
|
195
207
|
|
|
196
|
-
return
|
|
208
|
+
return (
|
|
209
|
+
<PageSizeContextProvider pageSizes={originalComponentProps.pageSizes}>
|
|
210
|
+
<Tabs tabs={tabs} toolbar={toolbar} />
|
|
211
|
+
</PageSizeContextProvider>
|
|
212
|
+
);
|
|
197
213
|
};
|
|
198
214
|
|
|
199
215
|
type ToolbarProps = {
|
|
@@ -208,6 +224,8 @@ type ToolbarProps = {
|
|
|
208
224
|
colorScale: ColorScale;
|
|
209
225
|
setColorScale: Dispatch<StateUpdater<ColorScale>>;
|
|
210
226
|
originalComponentProps: MutationsOverTimeProps;
|
|
227
|
+
mutationFilterValue: string;
|
|
228
|
+
setFilterValue: (filterValue: string) => void;
|
|
211
229
|
};
|
|
212
230
|
|
|
213
231
|
const Toolbar: FunctionComponent<ToolbarProps> = ({
|
|
@@ -222,9 +240,12 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
|
|
|
222
240
|
colorScale,
|
|
223
241
|
setColorScale,
|
|
224
242
|
originalComponentProps,
|
|
243
|
+
setFilterValue,
|
|
244
|
+
mutationFilterValue,
|
|
225
245
|
}) => {
|
|
226
246
|
return (
|
|
227
247
|
<>
|
|
248
|
+
<MutationsOverTimeTextFilter setFilterValue={setFilterValue} value={mutationFilterValue} />
|
|
228
249
|
{activeTab === 'Grid' && (
|
|
229
250
|
<ColorScaleSelectorDropdown colorScale={colorScale} setColorScale={setColorScale} />
|
|
230
251
|
)}
|