@genspectrum/dashboard-components 0.15.0 → 0.16.1
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/custom-elements.json +255 -57
- package/dist/components.d.ts +49 -32
- package/dist/components.js +361 -212
- package/dist/components.js.map +1 -1
- package/dist/style.css +9 -0
- package/dist/util.d.ts +43 -43
- package/package.json +1 -1
- package/src/preact/ReferenceGenomeContext.ts +16 -1
- package/src/preact/aggregatedData/aggregate-bar-chart.tsx +26 -5
- package/src/preact/aggregatedData/aggregate.stories.tsx +0 -1
- package/src/preact/aggregatedData/aggregate.tsx +5 -1
- package/src/preact/components/ReferenceGenomesAwaiter.tsx +1 -6
- package/src/preact/components/info.tsx +1 -0
- package/src/preact/components/resize-container.tsx +1 -1
- package/src/preact/mutationComparison/mutation-comparison-venn.tsx +4 -2
- package/src/preact/mutationComparison/mutation-comparison.stories.tsx +0 -1
- package/src/preact/mutationComparison/mutation-comparison.tsx +5 -1
- package/src/preact/mutationFilter/mutation-filter.stories.tsx +17 -1
- package/src/preact/mutationFilter/mutation-filter.tsx +8 -0
- package/src/preact/mutations/mutations.stories.tsx +0 -1
- package/src/preact/mutations/mutations.tsx +1 -1
- package/src/preact/mutationsOverTime/getFilteredMutationsOverTime.spec.ts +70 -14
- package/src/preact/mutationsOverTime/getFilteredMutationsOverTimeData.ts +30 -7
- package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +56 -55
- package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +26 -39
- package/src/preact/mutationsOverTime/mutations-over-time.tsx +22 -7
- package/src/preact/numberSequencesOverTime/number-sequences-over-time-bar-chart.tsx +8 -3
- package/src/preact/numberSequencesOverTime/number-sequences-over-time-line-chart.tsx +8 -3
- package/src/preact/numberSequencesOverTime/number-sequences-over-time.stories.tsx +3 -1
- package/src/preact/numberSequencesOverTime/number-sequences-over-time.tsx +18 -3
- package/src/preact/prevalenceOverTime/prevalence-over-time-bar-chart.tsx +48 -35
- package/src/preact/prevalenceOverTime/prevalence-over-time-bubble-chart.tsx +83 -70
- package/src/preact/prevalenceOverTime/prevalence-over-time-line-chart.tsx +48 -37
- package/src/preact/prevalenceOverTime/prevalence-over-time.stories.tsx +0 -3
- package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +6 -1
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage-chart.tsx +31 -23
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.stories.tsx +0 -1
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +5 -1
- package/src/preact/sequencesByLocation/__mockData__/worldAtlas.json +1 -0
- package/src/preact/{map → sequencesByLocation}/sequences-by-location-map.tsx +6 -3
- package/src/preact/{map → sequencesByLocation}/sequences-by-location-table.tsx +1 -1
- package/src/preact/{map → sequencesByLocation}/sequences-by-location.stories.tsx +58 -1
- package/src/preact/{map → sequencesByLocation}/sequences-by-location.tsx +10 -1
- package/src/preact/shared/aspectRatio/AspectRatio.tsx +13 -0
- package/src/preact/shared/charts/getMaintainAspectRatio.ts +3 -0
- package/src/preact/statistic/statistics.stories.tsx +0 -1
- package/src/preact/statistic/statistics.tsx +4 -4
- package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.stories.tsx +0 -1
- package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.tsx +1 -1
- package/src/query/computeMapLocationData.spec.ts +1 -1
- package/src/query/computeMapLocationData.ts +1 -1
- package/src/query/querySequencesByLocationData.ts +1 -1
- package/src/utilEntrypoint.ts +1 -1
- package/src/web-components/PreactLitAdapter.tsx +2 -5
- package/src/web-components/ResizeContainer.mdx +4 -1
- package/src/web-components/gs-app.ts +2 -4
- package/src/web-components/visualization/gs-aggregate.stories.ts +13 -6
- package/src/web-components/visualization/gs-aggregate.tsx +1 -1
- package/src/web-components/visualization/gs-mutation-comparison.stories.ts +8 -1
- package/src/web-components/visualization/gs-mutation-comparison.tsx +1 -1
- package/src/web-components/visualization/gs-mutations-over-time.stories.ts +24 -1
- package/src/web-components/visualization/gs-mutations-over-time.tsx +30 -1
- package/src/web-components/visualization/gs-mutations.stories.ts +8 -1
- package/src/web-components/visualization/gs-mutations.tsx +1 -1
- package/src/web-components/visualization/gs-number-sequences-over-time.stories.ts +11 -1
- package/src/web-components/visualization/gs-number-sequences-over-time.tsx +1 -1
- package/src/web-components/visualization/gs-prevalence-over-time.stories.ts +8 -2
- package/src/web-components/visualization/gs-prevalence-over-time.tsx +1 -1
- package/src/web-components/visualization/gs-relative-growth-advantage.stories.ts +8 -1
- package/src/web-components/visualization/gs-relative-growth-advantage.tsx +1 -1
- package/src/web-components/visualization/gs-sequences-by-location.stories.ts +13 -7
- package/src/web-components/visualization/gs-sequences-by-location.tsx +6 -3
- package/src/web-components/visualization/gs-statistics.stories.ts +0 -1
- package/src/web-components/visualization/gs-statistics.tsx +1 -1
- package/src/web-components/wastewaterVisualization/gs-wastewater-mutations-over-time.stories.ts +9 -1
- package/src/web-components/wastewaterVisualization/gs-wastewater-mutations-over-time.tsx +1 -1
- package/standalone-bundle/dashboard-components.js +5817 -5706
- package/standalone-bundle/dashboard-components.js.map +1 -1
- package/standalone-bundle/style.css +1 -1
- package/src/preact/map/__mockData__/worldAtlas.json +0 -497127
- /package/src/preact/{map → sequencesByLocation}/__mockData__/aggregatedGermany.json +0 -0
- /package/src/preact/{map → sequencesByLocation}/__mockData__/aggregatedWorld.json +0 -0
- /package/src/preact/{map → sequencesByLocation}/__mockData__/germanyMap.json +0 -0
- /package/src/preact/{map → sequencesByLocation}/__mockData__/howToGenerateWorldMap.md +0 -0
- /package/src/preact/{map → sequencesByLocation}/leafletStyleModifications.css +0 -0
- /package/src/preact/{map → sequencesByLocation}/loadMapSource.tsx +0 -0
|
@@ -21,6 +21,7 @@ import { ProportionSelectorDropdown } from '../components/proportion-selector-dr
|
|
|
21
21
|
import { ResizeContainer } from '../components/resize-container';
|
|
22
22
|
import { type DisplayedSegment, SegmentSelector, useDisplayedSegments } from '../components/segment-selector';
|
|
23
23
|
import Tabs from '../components/tabs';
|
|
24
|
+
import { getMaintainAspectRatio } from '../shared/charts/getMaintainAspectRatio';
|
|
24
25
|
import { useQuery } from '../useQuery';
|
|
25
26
|
|
|
26
27
|
export const mutationComparisonViewSchema = z.union([z.literal(views.table), z.literal(views.venn)]);
|
|
@@ -28,7 +29,7 @@ export type MutationComparisonView = z.infer<typeof mutationComparisonViewSchema
|
|
|
28
29
|
|
|
29
30
|
const mutationComparisonPropsSchema = z.object({
|
|
30
31
|
width: z.string(),
|
|
31
|
-
height: z.string(),
|
|
32
|
+
height: z.string().optional(),
|
|
32
33
|
lapisFilters: z.array(namedLapisFilterSchema).min(1),
|
|
33
34
|
sequenceType: sequenceTypeSchema,
|
|
34
35
|
views: z.array(mutationComparisonViewSchema),
|
|
@@ -91,6 +92,8 @@ const MutationComparisonTabs: FunctionComponent<MutationComparisonTabsProps> = (
|
|
|
91
92
|
[data, displayedSegments, displayedMutationTypes],
|
|
92
93
|
);
|
|
93
94
|
|
|
95
|
+
const maintainAspectRatio = getMaintainAspectRatio(originalComponentProps.height);
|
|
96
|
+
|
|
94
97
|
const getTab = (view: MutationComparisonView) => {
|
|
95
98
|
switch (view) {
|
|
96
99
|
case 'table':
|
|
@@ -111,6 +114,7 @@ const MutationComparisonTabs: FunctionComponent<MutationComparisonTabsProps> = (
|
|
|
111
114
|
<MutationComparisonVenn
|
|
112
115
|
data={{ content: filteredData }}
|
|
113
116
|
proportionInterval={proportionInterval}
|
|
117
|
+
maintainAspectRatio={maintainAspectRatio}
|
|
114
118
|
/>
|
|
115
119
|
),
|
|
116
120
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type Meta, type PreactRenderer, type StoryObj } from '@storybook/preact';
|
|
2
2
|
import { expect, fireEvent, fn, userEvent, waitFor, within } from '@storybook/test';
|
|
3
3
|
import { type StepFunction } from '@storybook/types';
|
|
4
4
|
|
|
@@ -8,6 +8,7 @@ import { LAPIS_URL } from '../../constants';
|
|
|
8
8
|
import referenceGenome from '../../lapisApi/__mockData__/referenceGenome.json';
|
|
9
9
|
import { LapisUrlContextProvider } from '../LapisUrlContext';
|
|
10
10
|
import { ReferenceGenomeContext } from '../ReferenceGenomeContext';
|
|
11
|
+
import { playThatExpectsErrorMessage } from '../shared/stories/expectErrorMessage';
|
|
11
12
|
|
|
12
13
|
const meta: Meta<MutationFilterProps> = {
|
|
13
14
|
title: 'Input/MutationFilter',
|
|
@@ -219,6 +220,21 @@ export const WithInitialValue: StoryObj<MutationFilterProps> = {
|
|
|
219
220
|
},
|
|
220
221
|
};
|
|
221
222
|
|
|
223
|
+
export const WithNoReferenceSequencesDefined: StoryObj<MutationFilterProps> = {
|
|
224
|
+
...Default,
|
|
225
|
+
render: (args) => (
|
|
226
|
+
<LapisUrlContextProvider value={LAPIS_URL}>
|
|
227
|
+
<ReferenceGenomeContext.Provider value={{ nucleotideSequences: [], genes: [] }}>
|
|
228
|
+
<MutationFilter {...args} />
|
|
229
|
+
</ReferenceGenomeContext.Provider>
|
|
230
|
+
</LapisUrlContextProvider>
|
|
231
|
+
),
|
|
232
|
+
play: playThatExpectsErrorMessage(
|
|
233
|
+
'Error - No reference sequences available',
|
|
234
|
+
'This organism has neither nucleotide nor amino acid sequences',
|
|
235
|
+
),
|
|
236
|
+
};
|
|
237
|
+
|
|
222
238
|
async function prepare(canvasElement: HTMLElement, step: StepFunction<PreactRenderer, unknown>) {
|
|
223
239
|
const canvas = within(canvasElement);
|
|
224
240
|
|
|
@@ -10,6 +10,7 @@ import { type MutationsFilter, mutationsFilterSchema } from '../../types';
|
|
|
10
10
|
import { type DeletionClass, type InsertionClass, type SubstitutionClass } from '../../utils/mutations';
|
|
11
11
|
import { ReferenceGenomeContext } from '../ReferenceGenomeContext';
|
|
12
12
|
import { ErrorBoundary } from '../components/error-boundary';
|
|
13
|
+
import { UserFacingError } from '../components/error-display';
|
|
13
14
|
import { singleGraphColorRGBByName } from '../shared/charts/colors';
|
|
14
15
|
|
|
15
16
|
const mutationFilterInnerPropsSchema = z.object({
|
|
@@ -54,6 +55,13 @@ export const MutationFilterInner: FunctionComponent<MutationFilterInnerProps> =
|
|
|
54
55
|
|
|
55
56
|
const filterRef = useRef<HTMLDivElement>(null);
|
|
56
57
|
|
|
58
|
+
if (referenceGenome.nucleotideSequences.length === 0 && referenceGenome.genes.length === 0) {
|
|
59
|
+
throw new UserFacingError(
|
|
60
|
+
'No reference sequences available',
|
|
61
|
+
'This organism has neither nucleotide nor amino acid sequences configured in its reference genome. You cannot filter by mutations.',
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
57
65
|
const handleRemoveValue = (option: ParsedMutationFilter) => {
|
|
58
66
|
const newSelectedFilters = {
|
|
59
67
|
...selectedFilters,
|
|
@@ -41,7 +41,7 @@ const mutationsPropsSchema = z.object({
|
|
|
41
41
|
views: mutationsViewSchema.array(),
|
|
42
42
|
pageSize: z.union([z.boolean(), z.number()]),
|
|
43
43
|
width: z.string(),
|
|
44
|
-
height: z.string(),
|
|
44
|
+
height: z.string().optional(),
|
|
45
45
|
});
|
|
46
46
|
export type MutationsProps = z.infer<typeof mutationsPropsSchema>;
|
|
47
47
|
|
|
@@ -15,16 +15,17 @@ describe('getFilteredMutationOverTimeData', () => {
|
|
|
15
15
|
someDeletionEntry,
|
|
16
16
|
]);
|
|
17
17
|
|
|
18
|
-
const result = getFilteredMutationOverTimeData(
|
|
18
|
+
const result = getFilteredMutationOverTimeData({
|
|
19
19
|
data,
|
|
20
20
|
overallMutationData,
|
|
21
|
-
[
|
|
21
|
+
displayedSegments: [
|
|
22
22
|
{ segment: 'someSegment', checked: false, label: 'Some Segment' },
|
|
23
23
|
{ segment: 'someOtherSegment', checked: true, label: 'Some Other Segment' },
|
|
24
24
|
],
|
|
25
|
-
[],
|
|
25
|
+
displayedMutationTypes: [],
|
|
26
26
|
proportionInterval,
|
|
27
|
-
|
|
27
|
+
displayMutations: undefined,
|
|
28
|
+
});
|
|
28
29
|
|
|
29
30
|
expect(result.getFirstAxisKeys()).to.deep.equal([anotherSubstitution]);
|
|
30
31
|
});
|
|
@@ -36,11 +37,11 @@ describe('getFilteredMutationOverTimeData', () => {
|
|
|
36
37
|
someDeletionEntry,
|
|
37
38
|
]);
|
|
38
39
|
|
|
39
|
-
const result = getFilteredMutationOverTimeData(
|
|
40
|
+
const result = getFilteredMutationOverTimeData({
|
|
40
41
|
data,
|
|
41
42
|
overallMutationData,
|
|
42
|
-
[],
|
|
43
|
-
[
|
|
43
|
+
displayedSegments: [],
|
|
44
|
+
displayedMutationTypes: [
|
|
44
45
|
{
|
|
45
46
|
type: 'substitution',
|
|
46
47
|
checked: false,
|
|
@@ -53,7 +54,7 @@ describe('getFilteredMutationOverTimeData', () => {
|
|
|
53
54
|
},
|
|
54
55
|
],
|
|
55
56
|
proportionInterval,
|
|
56
|
-
);
|
|
57
|
+
});
|
|
57
58
|
|
|
58
59
|
expect(result.getFirstAxisKeys()).to.deep.equal([someDeletion]);
|
|
59
60
|
});
|
|
@@ -65,7 +66,13 @@ describe('getFilteredMutationOverTimeData', () => {
|
|
|
65
66
|
{ ...someDeletionEntry, proportion: inFilter },
|
|
66
67
|
]);
|
|
67
68
|
|
|
68
|
-
const result = getFilteredMutationOverTimeData(
|
|
69
|
+
const result = getFilteredMutationOverTimeData({
|
|
70
|
+
data,
|
|
71
|
+
overallMutationData,
|
|
72
|
+
displayedSegments: [],
|
|
73
|
+
displayedMutationTypes: [],
|
|
74
|
+
proportionInterval,
|
|
75
|
+
});
|
|
69
76
|
|
|
70
77
|
expect(result.getFirstAxisKeys()).to.deep.equal([anotherSubstitution, someDeletion]);
|
|
71
78
|
});
|
|
@@ -77,7 +84,13 @@ describe('getFilteredMutationOverTimeData', () => {
|
|
|
77
84
|
{ ...someDeletionEntry, proportion: inFilter },
|
|
78
85
|
]);
|
|
79
86
|
|
|
80
|
-
const result = getFilteredMutationOverTimeData(
|
|
87
|
+
const result = getFilteredMutationOverTimeData({
|
|
88
|
+
data,
|
|
89
|
+
overallMutationData,
|
|
90
|
+
displayedSegments: [],
|
|
91
|
+
displayedMutationTypes: [],
|
|
92
|
+
proportionInterval,
|
|
93
|
+
});
|
|
81
94
|
|
|
82
95
|
expect(result.getFirstAxisKeys()).to.deep.equal([anotherSubstitution, someDeletion]);
|
|
83
96
|
});
|
|
@@ -90,7 +103,13 @@ describe('getFilteredMutationOverTimeData', () => {
|
|
|
90
103
|
]);
|
|
91
104
|
data.set(someSubstitution, someTemporal, { ...someMutationOverTimeValue, proportion: belowFilter });
|
|
92
105
|
|
|
93
|
-
const result = getFilteredMutationOverTimeData(
|
|
106
|
+
const result = getFilteredMutationOverTimeData({
|
|
107
|
+
data,
|
|
108
|
+
overallMutationData,
|
|
109
|
+
displayedSegments: [],
|
|
110
|
+
displayedMutationTypes: [],
|
|
111
|
+
proportionInterval,
|
|
112
|
+
});
|
|
94
113
|
|
|
95
114
|
expect(result.getFirstAxisKeys()).to.deep.equal([someSubstitution, anotherSubstitution, someDeletion]);
|
|
96
115
|
});
|
|
@@ -103,7 +122,13 @@ describe('getFilteredMutationOverTimeData', () => {
|
|
|
103
122
|
]);
|
|
104
123
|
data.set(someSubstitution, someTemporal, { ...someMutationOverTimeValue, proportion: aboveFilter });
|
|
105
124
|
|
|
106
|
-
const result = getFilteredMutationOverTimeData(
|
|
125
|
+
const result = getFilteredMutationOverTimeData({
|
|
126
|
+
data,
|
|
127
|
+
overallMutationData,
|
|
128
|
+
displayedSegments: [],
|
|
129
|
+
displayedMutationTypes: [],
|
|
130
|
+
proportionInterval,
|
|
131
|
+
});
|
|
107
132
|
|
|
108
133
|
expect(result.getFirstAxisKeys()).to.deep.equal([someSubstitution, anotherSubstitution, someDeletion]);
|
|
109
134
|
});
|
|
@@ -114,7 +139,13 @@ describe('getFilteredMutationOverTimeData', () => {
|
|
|
114
139
|
{ ...anotherSubstitutionEntry, proportion: inFilter },
|
|
115
140
|
{ ...someDeletionEntry, proportion: inFilter },
|
|
116
141
|
]);
|
|
117
|
-
const result = getFilteredMutationOverTimeData(
|
|
142
|
+
const result = getFilteredMutationOverTimeData({
|
|
143
|
+
data,
|
|
144
|
+
overallMutationData,
|
|
145
|
+
displayedSegments: [],
|
|
146
|
+
displayedMutationTypes: [],
|
|
147
|
+
proportionInterval,
|
|
148
|
+
});
|
|
118
149
|
|
|
119
150
|
expect(result.getFirstAxisKeys()).to.deep.equal([someSubstitution, anotherSubstitution, someDeletion]);
|
|
120
151
|
});
|
|
@@ -126,11 +157,36 @@ describe('getFilteredMutationOverTimeData', () => {
|
|
|
126
157
|
{ ...someDeletionEntry, proportion: inFilter },
|
|
127
158
|
]);
|
|
128
159
|
|
|
129
|
-
const result = getFilteredMutationOverTimeData(
|
|
160
|
+
const result = getFilteredMutationOverTimeData({
|
|
161
|
+
data,
|
|
162
|
+
overallMutationData,
|
|
163
|
+
displayedSegments: [],
|
|
164
|
+
displayedMutationTypes: [],
|
|
165
|
+
proportionInterval,
|
|
166
|
+
});
|
|
130
167
|
|
|
131
168
|
expect(result.getFirstAxisKeys()).to.deep.equal([someSubstitution, anotherSubstitution, someDeletion]);
|
|
132
169
|
});
|
|
133
170
|
|
|
171
|
+
it('should filter by displayMutations', () => {
|
|
172
|
+
const { data, overallMutationData } = prepareMutationOverTimeData([
|
|
173
|
+
someSubstitutionEntry,
|
|
174
|
+
anotherSubstitutionEntry,
|
|
175
|
+
someDeletionEntry,
|
|
176
|
+
]);
|
|
177
|
+
|
|
178
|
+
const result = getFilteredMutationOverTimeData({
|
|
179
|
+
data,
|
|
180
|
+
overallMutationData,
|
|
181
|
+
displayedSegments: [],
|
|
182
|
+
displayedMutationTypes: [],
|
|
183
|
+
proportionInterval,
|
|
184
|
+
displayMutations: [anotherSubstitution.code, someDeletion.code],
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
expect(result.getFirstAxisKeys()).to.deep.equal([anotherSubstitution, someDeletion]);
|
|
188
|
+
});
|
|
189
|
+
|
|
134
190
|
const belowFilter = 0.1;
|
|
135
191
|
const atFilterMin = 0.2;
|
|
136
192
|
const inFilter = 0.5;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import z from 'zod';
|
|
2
|
+
|
|
1
3
|
import { type MutationOverTimeDataMap } from './MutationOverTimeData';
|
|
2
4
|
import { type SubstitutionOrDeletionEntry } from '../../types';
|
|
3
5
|
import { Map2dView } from '../../utils/map2d';
|
|
@@ -5,15 +7,31 @@ import type { Deletion, Substitution } from '../../utils/mutations';
|
|
|
5
7
|
import type { DisplayedMutationType } from '../components/mutation-type-selector';
|
|
6
8
|
import type { DisplayedSegment } from '../components/segment-selector';
|
|
7
9
|
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
export const displayMutationsSchema = z.array(z.string()).min(1);
|
|
11
|
+
export type DisplayMutations = z.infer<typeof displayMutationsSchema>;
|
|
12
|
+
|
|
13
|
+
export type GetFilteredMutationOverTimeDataArgs = {
|
|
14
|
+
data: MutationOverTimeDataMap;
|
|
15
|
+
overallMutationData: SubstitutionOrDeletionEntry<Substitution, Deletion>[];
|
|
16
|
+
displayedSegments: DisplayedSegment[];
|
|
17
|
+
displayedMutationTypes: DisplayedMutationType[];
|
|
18
|
+
proportionInterval: { min: number; max: number };
|
|
19
|
+
displayMutations?: DisplayMutations;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function getFilteredMutationOverTimeData({
|
|
23
|
+
data,
|
|
24
|
+
overallMutationData,
|
|
25
|
+
displayedSegments,
|
|
26
|
+
displayedMutationTypes,
|
|
27
|
+
proportionInterval,
|
|
28
|
+
displayMutations,
|
|
29
|
+
}: GetFilteredMutationOverTimeDataArgs) {
|
|
15
30
|
const filteredData = new Map2dView(data);
|
|
16
31
|
|
|
32
|
+
const displayMutationsSet =
|
|
33
|
+
displayMutations === undefined ? null : new Set(displayMutations.map((it) => it.toUpperCase()));
|
|
34
|
+
|
|
17
35
|
const mutationsToFilterOut = overallMutationData.filter((entry) => {
|
|
18
36
|
if (entry.proportion < proportionInterval.min || entry.proportion > proportionInterval.max) {
|
|
19
37
|
return true;
|
|
@@ -21,6 +39,11 @@ export function getFilteredMutationOverTimeData(
|
|
|
21
39
|
if (displayedSegments.some((segment) => segment.segment === entry.mutation.segment && !segment.checked)) {
|
|
22
40
|
return true;
|
|
23
41
|
}
|
|
42
|
+
|
|
43
|
+
if (displayMutationsSet !== null && !displayMutationsSet.has(entry.mutation.code)) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
|
|
24
47
|
return displayedMutationTypes.some(
|
|
25
48
|
(mutationType) => mutationType.type === entry.mutation.type && !mutationType.checked,
|
|
26
49
|
);
|
|
@@ -39,63 +39,64 @@ const MutationsOverTimeGrid: FunctionComponent<MutationsOverTimeGridProps> = ({
|
|
|
39
39
|
to reduce the number of mutations.
|
|
40
40
|
</div>
|
|
41
41
|
)}
|
|
42
|
-
{allMutations.length === 0
|
|
42
|
+
{allMutations.length === 0 ? (
|
|
43
43
|
<div className={'flex justify-center'}>No data available for your filters.</div>
|
|
44
|
+
) : (
|
|
45
|
+
<div
|
|
46
|
+
ref={gridRef}
|
|
47
|
+
style={{
|
|
48
|
+
display: 'grid',
|
|
49
|
+
gridTemplateRows: `repeat(${shownMutations.length}, 24px)`,
|
|
50
|
+
gridTemplateColumns: `${MUTATION_CELL_WIDTH_REM}rem repeat(${dates.length}, minmax(0.05rem, 1fr))`,
|
|
51
|
+
}}
|
|
52
|
+
className='text-center'
|
|
53
|
+
>
|
|
54
|
+
{dates.map((date, columnIndex) => (
|
|
55
|
+
<div
|
|
56
|
+
className='@container font-semibold'
|
|
57
|
+
style={{ gridRowStart: 1, gridColumnStart: columnIndex + 2 }}
|
|
58
|
+
key={date.dateString}
|
|
59
|
+
>
|
|
60
|
+
<p {...styleGridHeader(columnIndex, dates)}>{date.dateString}</p>
|
|
61
|
+
</div>
|
|
62
|
+
))}
|
|
63
|
+
{shownMutations.map((mutation, rowIndex) => {
|
|
64
|
+
return (
|
|
65
|
+
<Fragment key={`fragment-${mutation.code}`}>
|
|
66
|
+
<div
|
|
67
|
+
key={`mutation-${mutation.code}`}
|
|
68
|
+
style={{ gridRowStart: rowIndex + 2, gridColumnStart: 1 }}
|
|
69
|
+
>
|
|
70
|
+
<MutationCell mutation={mutation} />
|
|
71
|
+
</div>
|
|
72
|
+
{dates.map((date, columnIndex) => {
|
|
73
|
+
const value = data.get(mutation, date) ?? null;
|
|
74
|
+
const tooltipPosition = getTooltipPosition(
|
|
75
|
+
rowIndex,
|
|
76
|
+
shownMutations.length,
|
|
77
|
+
columnIndex,
|
|
78
|
+
dates.length,
|
|
79
|
+
);
|
|
80
|
+
return (
|
|
81
|
+
<div
|
|
82
|
+
style={{ gridRowStart: rowIndex + 2, gridColumnStart: columnIndex + 2 }}
|
|
83
|
+
key={`${mutation.code}-${date.dateString}`}
|
|
84
|
+
>
|
|
85
|
+
<ProportionCell
|
|
86
|
+
value={value}
|
|
87
|
+
date={date}
|
|
88
|
+
mutation={mutation}
|
|
89
|
+
tooltipPosition={tooltipPosition}
|
|
90
|
+
colorScale={colorScale}
|
|
91
|
+
/>
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
})}
|
|
95
|
+
</Fragment>
|
|
96
|
+
);
|
|
97
|
+
})}
|
|
98
|
+
</div>
|
|
44
99
|
)}
|
|
45
|
-
<div
|
|
46
|
-
ref={gridRef}
|
|
47
|
-
style={{
|
|
48
|
-
display: 'grid',
|
|
49
|
-
gridTemplateRows: `repeat(${shownMutations.length}, 24px)`,
|
|
50
|
-
gridTemplateColumns: `${MUTATION_CELL_WIDTH_REM}rem repeat(${dates.length}, minmax(0.05rem, 1fr))`,
|
|
51
|
-
}}
|
|
52
|
-
className='text-center'
|
|
53
|
-
>
|
|
54
|
-
{dates.map((date, columnIndex) => (
|
|
55
|
-
<div
|
|
56
|
-
className='@container font-semibold'
|
|
57
|
-
style={{ gridRowStart: 1, gridColumnStart: columnIndex + 2 }}
|
|
58
|
-
key={date.dateString}
|
|
59
|
-
>
|
|
60
|
-
<p {...styleGridHeader(columnIndex, dates)}>{date.dateString}</p>
|
|
61
|
-
</div>
|
|
62
|
-
))}
|
|
63
|
-
{shownMutations.map((mutation, rowIndex) => {
|
|
64
|
-
return (
|
|
65
|
-
<Fragment key={`fragment-${mutation.code}`}>
|
|
66
|
-
<div
|
|
67
|
-
key={`mutation-${mutation.code}`}
|
|
68
|
-
style={{ gridRowStart: rowIndex + 2, gridColumnStart: 1 }}
|
|
69
|
-
>
|
|
70
|
-
<MutationCell mutation={mutation} />
|
|
71
|
-
</div>
|
|
72
|
-
{dates.map((date, columnIndex) => {
|
|
73
|
-
const value = data.get(mutation, date) ?? null;
|
|
74
|
-
const tooltipPosition = getTooltipPosition(
|
|
75
|
-
rowIndex,
|
|
76
|
-
shownMutations.length,
|
|
77
|
-
columnIndex,
|
|
78
|
-
dates.length,
|
|
79
|
-
);
|
|
80
|
-
return (
|
|
81
|
-
<div
|
|
82
|
-
style={{ gridRowStart: rowIndex + 2, gridColumnStart: columnIndex + 2 }}
|
|
83
|
-
key={`${mutation.code}-${date.dateString}`}
|
|
84
|
-
>
|
|
85
|
-
<ProportionCell
|
|
86
|
-
value={value}
|
|
87
|
-
date={date}
|
|
88
|
-
mutation={mutation}
|
|
89
|
-
tooltipPosition={tooltipPosition}
|
|
90
|
-
colorScale={colorScale}
|
|
91
|
-
/>
|
|
92
|
-
</div>
|
|
93
|
-
);
|
|
94
|
-
})}
|
|
95
|
-
</Fragment>
|
|
96
|
-
);
|
|
97
|
-
})}
|
|
98
|
-
</div>
|
|
99
100
|
</>
|
|
100
101
|
);
|
|
101
102
|
};
|
|
@@ -28,6 +28,8 @@ const meta: Meta<MutationsOverTimeProps> = {
|
|
|
28
28
|
control: { type: 'radio' },
|
|
29
29
|
},
|
|
30
30
|
lapisDateField: { control: 'text' },
|
|
31
|
+
displayMutations: { control: 'object' },
|
|
32
|
+
initialMeanProportionInterval: { control: 'object' },
|
|
31
33
|
},
|
|
32
34
|
parameters: {
|
|
33
35
|
fetchMock: {},
|
|
@@ -36,49 +38,32 @@ const meta: Meta<MutationsOverTimeProps> = {
|
|
|
36
38
|
|
|
37
39
|
export default meta;
|
|
38
40
|
|
|
39
|
-
const
|
|
41
|
+
export const Default: StoryObj<MutationsOverTimeProps> = {
|
|
40
42
|
render: (args: MutationsOverTimeProps) => (
|
|
41
43
|
<LapisUrlContextProvider value={LAPIS_URL}>
|
|
42
44
|
<ReferenceGenomeContext.Provider value={referenceGenome}>
|
|
43
|
-
<MutationsOverTime
|
|
44
|
-
lapisFilter={args.lapisFilter}
|
|
45
|
-
sequenceType={args.sequenceType}
|
|
46
|
-
views={args.views}
|
|
47
|
-
width={args.width}
|
|
48
|
-
height={args.height}
|
|
49
|
-
granularity={args.granularity}
|
|
50
|
-
lapisDateField={args.lapisDateField}
|
|
51
|
-
/>
|
|
45
|
+
<MutationsOverTime {...args} />
|
|
52
46
|
</ReferenceGenomeContext.Provider>
|
|
53
47
|
</LapisUrlContextProvider>
|
|
54
48
|
),
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
// This test uses mock data: defaultMockData.ts (through mutationOverTimeWorker.mock.ts)
|
|
58
|
-
export const Default: StoryObj<MutationsOverTimeProps> = {
|
|
59
|
-
...Template,
|
|
60
49
|
args: {
|
|
61
50
|
lapisFilter: { pangoLineage: 'JN.1*', dateFrom: '2024-01-15', dateTo: '2024-07-10' },
|
|
62
51
|
sequenceType: 'nucleotide',
|
|
63
52
|
views: ['grid'],
|
|
64
53
|
width: '100%',
|
|
65
|
-
height: '700px',
|
|
66
54
|
granularity: 'month',
|
|
67
55
|
lapisDateField: 'date',
|
|
56
|
+
initialMeanProportionInterval: { min: 0.05, max: 0.9 },
|
|
68
57
|
},
|
|
69
58
|
};
|
|
70
59
|
|
|
71
60
|
// This test uses mock data: showMessagWhenTooManyMutations.ts (through mutationOverTimeWorker.mock.ts)
|
|
72
61
|
export const ShowsMessageWhenTooManyMutations: StoryObj<MutationsOverTimeProps> = {
|
|
73
|
-
...
|
|
62
|
+
...Default,
|
|
74
63
|
args: {
|
|
64
|
+
...Default.args,
|
|
75
65
|
lapisFilter: { dateFrom: '2023-01-01', dateTo: '2023-12-31' },
|
|
76
|
-
sequenceType: 'nucleotide',
|
|
77
|
-
views: ['grid'],
|
|
78
|
-
width: '100%',
|
|
79
|
-
height: '700px',
|
|
80
66
|
granularity: 'year',
|
|
81
|
-
lapisDateField: 'date',
|
|
82
67
|
},
|
|
83
68
|
play: async ({ canvas }) => {
|
|
84
69
|
await waitFor(() => expect(canvas.getByText('Showing 100 of 137 mutations.', { exact: false })).toBeVisible(), {
|
|
@@ -88,15 +73,12 @@ export const ShowsMessageWhenTooManyMutations: StoryObj<MutationsOverTimeProps>
|
|
|
88
73
|
};
|
|
89
74
|
|
|
90
75
|
export const ShowsNoDataWhenNoMutationsAreInFilter: StoryObj<MutationsOverTimeProps> = {
|
|
91
|
-
...
|
|
76
|
+
...Default,
|
|
92
77
|
args: {
|
|
78
|
+
...Default.args,
|
|
93
79
|
lapisFilter: { dateFrom: '1800-01-01', dateTo: '1800-01-02' },
|
|
94
|
-
sequenceType: 'nucleotide',
|
|
95
|
-
views: ['grid'],
|
|
96
|
-
width: '100%',
|
|
97
80
|
height: '700px',
|
|
98
81
|
granularity: 'year',
|
|
99
|
-
lapisDateField: 'date',
|
|
100
82
|
},
|
|
101
83
|
play: async ({ canvas }) => {
|
|
102
84
|
await waitFor(() => expect(canvas.getByText('No data available.', { exact: false })).toBeVisible(), {
|
|
@@ -106,15 +88,12 @@ export const ShowsNoDataWhenNoMutationsAreInFilter: StoryObj<MutationsOverTimePr
|
|
|
106
88
|
};
|
|
107
89
|
|
|
108
90
|
export const ShowsNoDataMessageWhenThereAreNoDatesInFilter: StoryObj<MutationsOverTimeProps> = {
|
|
109
|
-
...
|
|
91
|
+
...Default,
|
|
110
92
|
args: {
|
|
93
|
+
...Default.args,
|
|
111
94
|
lapisFilter: { dateFrom: '2345-01-01', dateTo: '2020-01-02' },
|
|
112
|
-
sequenceType: 'nucleotide',
|
|
113
|
-
views: ['grid'],
|
|
114
|
-
width: '100%',
|
|
115
95
|
height: '700px',
|
|
116
96
|
granularity: 'year',
|
|
117
|
-
lapisDateField: 'date',
|
|
118
97
|
},
|
|
119
98
|
play: async ({ canvas }) => {
|
|
120
99
|
await waitFor(() => expect(canvas.getByText('No data available.', { exact: false })).toBeVisible(), {
|
|
@@ -124,15 +103,10 @@ export const ShowsNoDataMessageWhenThereAreNoDatesInFilter: StoryObj<MutationsOv
|
|
|
124
103
|
};
|
|
125
104
|
|
|
126
105
|
export const ShowsNoDataMessageForStrictFilters: StoryObj<MutationsOverTimeProps> = {
|
|
127
|
-
...
|
|
106
|
+
...Default,
|
|
128
107
|
args: {
|
|
108
|
+
...Default.args,
|
|
129
109
|
lapisFilter: { pangoLineage: 'JN.1*', dateFrom: '2024-01-15', dateTo: '2024-07-10' },
|
|
130
|
-
sequenceType: 'nucleotide',
|
|
131
|
-
views: ['grid'],
|
|
132
|
-
width: '100%',
|
|
133
|
-
height: '700px',
|
|
134
|
-
granularity: 'month',
|
|
135
|
-
lapisDateField: 'date',
|
|
136
110
|
},
|
|
137
111
|
play: async ({ canvas }) => {
|
|
138
112
|
await waitFor(() => expect(canvas.getByText('Grid')).toBeVisible(), { timeout: 10000 });
|
|
@@ -157,6 +131,19 @@ export const ShowsNoDataMessageForStrictFilters: StoryObj<MutationsOverTimeProps
|
|
|
157
131
|
},
|
|
158
132
|
};
|
|
159
133
|
|
|
134
|
+
export const ShowsNoDataForStrictInitialProportionInterval: StoryObj<MutationsOverTimeProps> = {
|
|
135
|
+
...ShowsNoDataMessageForStrictFilters,
|
|
136
|
+
args: {
|
|
137
|
+
...ShowsNoDataMessageForStrictFilters.args,
|
|
138
|
+
initialMeanProportionInterval: { min: 0.4, max: 0.41 },
|
|
139
|
+
},
|
|
140
|
+
play: async ({ canvas }) => {
|
|
141
|
+
await waitFor(() =>
|
|
142
|
+
expect(canvas.getByText('No data available for your filters.', { exact: false })).toBeVisible(),
|
|
143
|
+
);
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
|
|
160
147
|
export const WithNoLapisDateFieldField: StoryObj<MutationsOverTimeProps> = {
|
|
161
148
|
...Default,
|
|
162
149
|
args: {
|
|
@@ -5,7 +5,7 @@ import z from 'zod';
|
|
|
5
5
|
// @ts-expect-error -- uses subpath imports and vite worker import
|
|
6
6
|
import MutationOverTimeWorker from '#mutationOverTime?worker&inline';
|
|
7
7
|
import { BaseMutationOverTimeDataMap, type MutationOverTimeDataMap } from './MutationOverTimeData';
|
|
8
|
-
import { getFilteredMutationOverTimeData } from './getFilteredMutationsOverTimeData';
|
|
8
|
+
import { displayMutationsSchema, getFilteredMutationOverTimeData } from './getFilteredMutationsOverTimeData';
|
|
9
9
|
import { type MutationOverTimeWorkerResponse } from './mutationOverTimeWorker';
|
|
10
10
|
import MutationsOverTimeGrid from './mutations-over-time-grid';
|
|
11
11
|
import { type MutationOverTimeQuery } from '../../query/queryMutationsOverTime';
|
|
@@ -44,8 +44,13 @@ const mutationOverTimeSchema = z.object({
|
|
|
44
44
|
views: z.array(mutationsOverTimeViewSchema),
|
|
45
45
|
granularity: temporalGranularitySchema,
|
|
46
46
|
lapisDateField: z.string().min(1),
|
|
47
|
+
displayMutations: displayMutationsSchema.optional(),
|
|
48
|
+
initialMeanProportionInterval: z.object({
|
|
49
|
+
min: z.number().min(0).max(1),
|
|
50
|
+
max: z.number().min(0).max(1),
|
|
51
|
+
}),
|
|
47
52
|
width: z.string(),
|
|
48
|
-
height: z.string(),
|
|
53
|
+
height: z.string().optional(),
|
|
49
54
|
});
|
|
50
55
|
export type MutationsOverTimeProps = z.infer<typeof mutationOverTimeSchema>;
|
|
51
56
|
|
|
@@ -116,7 +121,7 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
|
|
|
116
121
|
originalComponentProps,
|
|
117
122
|
overallMutationData,
|
|
118
123
|
}) => {
|
|
119
|
-
const [proportionInterval, setProportionInterval] = useState(
|
|
124
|
+
const [proportionInterval, setProportionInterval] = useState(originalComponentProps.initialMeanProportionInterval);
|
|
120
125
|
const [colorScale, setColorScale] = useState<ColorScale>({ min: 0, max: 1, color: 'indigo' });
|
|
121
126
|
|
|
122
127
|
const [displayedSegments, setDisplayedSegments] = useDisplayedSegments(originalComponentProps.sequenceType);
|
|
@@ -125,15 +130,25 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
|
|
|
125
130
|
{ label: 'Deletions', checked: true, type: 'deletion' },
|
|
126
131
|
]);
|
|
127
132
|
|
|
133
|
+
const displayMutations = originalComponentProps.displayMutations;
|
|
134
|
+
|
|
128
135
|
const filteredData = useMemo(() => {
|
|
129
|
-
return getFilteredMutationOverTimeData(
|
|
130
|
-
mutationOverTimeData,
|
|
136
|
+
return getFilteredMutationOverTimeData({
|
|
137
|
+
data: mutationOverTimeData,
|
|
131
138
|
overallMutationData,
|
|
132
139
|
displayedSegments,
|
|
133
140
|
displayedMutationTypes,
|
|
134
141
|
proportionInterval,
|
|
135
|
-
|
|
136
|
-
|
|
142
|
+
displayMutations,
|
|
143
|
+
});
|
|
144
|
+
}, [
|
|
145
|
+
mutationOverTimeData,
|
|
146
|
+
overallMutationData,
|
|
147
|
+
displayedSegments,
|
|
148
|
+
displayedMutationTypes,
|
|
149
|
+
proportionInterval,
|
|
150
|
+
displayMutations,
|
|
151
|
+
]);
|
|
137
152
|
|
|
138
153
|
const getTab = (view: MutationsOverTimeView) => {
|
|
139
154
|
if (filteredData === undefined) {
|