@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.
- package/README.md +12 -0
- package/custom-elements.json +1 -1
- package/dist/components.d.ts +44 -44
- package/dist/components.js +826 -343
- package/dist/components.js.map +1 -1
- package/dist/style.css +2 -2
- package/dist/util.d.ts +44 -44
- package/package.json +2 -2
- package/src/preact/MutationAnnotationsContext.tsx +34 -27
- package/src/preact/components/dropdown.tsx +1 -1
- package/src/preact/components/info.tsx +1 -1
- 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.stories.tsx +12 -5
- package/src/preact/components/segment-selector.tsx +11 -7
- package/src/preact/mutationComparison/mutation-comparison.tsx +5 -1
- 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/mutations/mutations.tsx +5 -1
- 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 +9 -12
- package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +27 -0
- package/src/preact/mutationsOverTime/mutations-over-time.tsx +31 -6
- package/src/preact/sequencesByLocation/__mockData__/worldAtlas.json +1 -1
- package/src/preact/shared/tanstackTable/pagination-context.tsx +30 -0
- package/src/preact/shared/tanstackTable/pagination.tsx +41 -21
- package/src/preact/shared/tanstackTable/tanstackTable.tsx +17 -3
- package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.stories.tsx +22 -4
- package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.tsx +11 -2
- 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/dashboard-components.js +12896 -13334
- package/standalone-bundle/dashboard-components.js.map +1 -1
- 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: '
|
|
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
|
],
|
|
@@ -185,7 +185,11 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
|
|
|
185
185
|
}) => {
|
|
186
186
|
return (
|
|
187
187
|
<>
|
|
188
|
-
<SegmentSelector
|
|
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
|
|
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
|
|
|
@@ -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];
|