@genspectrum/dashboard-components 0.16.0 → 0.16.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/custom-elements.json +56 -2
- package/dist/components.d.ts +28 -11
- package/dist/components.js +64 -32
- package/dist/components.js.map +1 -1
- package/dist/util.d.ts +11 -11
- package/package.json +1 -1
- package/src/preact/ReferenceGenomeContext.ts +4 -2
- package/src/preact/components/downshift-combobox.tsx +6 -4
- package/src/preact/components/info.tsx +1 -0
- 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 -37
- package/src/preact/mutationsOverTime/mutations-over-time.tsx +21 -6
- package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.tsx +1 -1
- package/src/web-components/PreactLitAdapter.tsx +2 -5
- package/src/web-components/gs-app.ts +2 -4
- package/src/web-components/visualization/gs-mutations-over-time.stories.ts +15 -0
- package/src/web-components/visualization/gs-mutations-over-time.tsx +29 -0
- package/standalone-bundle/dashboard-components.js +4835 -4812
- package/standalone-bundle/dashboard-components.js.map +1 -1
package/dist/util.d.ts
CHANGED
|
@@ -912,7 +912,7 @@ declare global {
|
|
|
912
912
|
|
|
913
913
|
declare global {
|
|
914
914
|
interface HTMLElementTagNameMap {
|
|
915
|
-
'gs-
|
|
915
|
+
'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
|
|
916
916
|
}
|
|
917
917
|
}
|
|
918
918
|
|
|
@@ -920,7 +920,7 @@ declare global {
|
|
|
920
920
|
declare global {
|
|
921
921
|
namespace JSX {
|
|
922
922
|
interface IntrinsicElements {
|
|
923
|
-
'gs-
|
|
923
|
+
'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
924
924
|
}
|
|
925
925
|
}
|
|
926
926
|
}
|
|
@@ -928,7 +928,7 @@ declare global {
|
|
|
928
928
|
|
|
929
929
|
declare global {
|
|
930
930
|
interface HTMLElementTagNameMap {
|
|
931
|
-
'gs-
|
|
931
|
+
'gs-aggregate': AggregateComponent;
|
|
932
932
|
}
|
|
933
933
|
}
|
|
934
934
|
|
|
@@ -936,7 +936,7 @@ declare global {
|
|
|
936
936
|
declare global {
|
|
937
937
|
namespace JSX {
|
|
938
938
|
interface IntrinsicElements {
|
|
939
|
-
'gs-
|
|
939
|
+
'gs-aggregate': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
940
940
|
}
|
|
941
941
|
}
|
|
942
942
|
}
|
|
@@ -992,11 +992,10 @@ declare global {
|
|
|
992
992
|
|
|
993
993
|
declare global {
|
|
994
994
|
interface HTMLElementTagNameMap {
|
|
995
|
-
'gs-
|
|
995
|
+
'gs-location-filter': LocationFilterComponent;
|
|
996
996
|
}
|
|
997
997
|
interface HTMLElementEventMap {
|
|
998
|
-
'gs-
|
|
999
|
-
'gs-date-range-option-changed': DateRangeOptionChangedEvent;
|
|
998
|
+
'gs-location-changed': LocationChangedEvent;
|
|
1000
999
|
}
|
|
1001
1000
|
}
|
|
1002
1001
|
|
|
@@ -1004,7 +1003,7 @@ declare global {
|
|
|
1004
1003
|
declare global {
|
|
1005
1004
|
namespace JSX {
|
|
1006
1005
|
interface IntrinsicElements {
|
|
1007
|
-
'gs-
|
|
1006
|
+
'gs-location-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1008
1007
|
}
|
|
1009
1008
|
}
|
|
1010
1009
|
}
|
|
@@ -1012,10 +1011,11 @@ declare global {
|
|
|
1012
1011
|
|
|
1013
1012
|
declare global {
|
|
1014
1013
|
interface HTMLElementTagNameMap {
|
|
1015
|
-
'gs-
|
|
1014
|
+
'gs-date-range-filter': DateRangeFilterComponent;
|
|
1016
1015
|
}
|
|
1017
1016
|
interface HTMLElementEventMap {
|
|
1018
|
-
'gs-
|
|
1017
|
+
'gs-date-range-filter-changed': CustomEvent<Record<string, string>>;
|
|
1018
|
+
'gs-date-range-option-changed': DateRangeOptionChangedEvent;
|
|
1019
1019
|
}
|
|
1020
1020
|
}
|
|
1021
1021
|
|
|
@@ -1023,7 +1023,7 @@ declare global {
|
|
|
1023
1023
|
declare global {
|
|
1024
1024
|
namespace JSX {
|
|
1025
1025
|
interface IntrinsicElements {
|
|
1026
|
-
'gs-
|
|
1026
|
+
'gs-date-range-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1027
1027
|
}
|
|
1028
1028
|
}
|
|
1029
1029
|
}
|
package/package.json
CHANGED
|
@@ -4,10 +4,12 @@ import { type ReferenceGenome } from '../lapisApi/ReferenceGenome';
|
|
|
4
4
|
|
|
5
5
|
const UNINITIALIZED_SEQUENCE = '__uninitialized__';
|
|
6
6
|
|
|
7
|
-
export const
|
|
7
|
+
export const INITIAL_REFERENCE_GENOMES = {
|
|
8
8
|
nucleotideSequences: [{ name: UNINITIALIZED_SEQUENCE, sequence: '' }],
|
|
9
9
|
genes: [],
|
|
10
|
-
}
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const ReferenceGenomeContext = createContext<ReferenceGenome>(INITIAL_REFERENCE_GENOMES);
|
|
11
13
|
|
|
12
14
|
export function isNotInitialized(referenceGenome: ReferenceGenome) {
|
|
13
15
|
return (
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useCombobox } from 'downshift/preact';
|
|
2
2
|
import { type ComponentChild } from 'preact';
|
|
3
|
-
import { useRef, useState } from 'preact/hooks';
|
|
3
|
+
import { useMemo, useRef, useState } from 'preact/hooks';
|
|
4
4
|
|
|
5
5
|
export function DownshiftCombobox<Item>({
|
|
6
6
|
allItems,
|
|
@@ -21,8 +21,10 @@ export function DownshiftCombobox<Item>({
|
|
|
21
21
|
}) {
|
|
22
22
|
const initialSelectedItem = value ?? null;
|
|
23
23
|
|
|
24
|
-
const [
|
|
25
|
-
|
|
24
|
+
const [itemsFilter, setItemsFilter] = useState(itemToString(initialSelectedItem));
|
|
25
|
+
const items = useMemo(
|
|
26
|
+
() => allItems.filter((item) => filterItemsByInputValue(item, itemsFilter)),
|
|
27
|
+
[allItems, filterItemsByInputValue, itemsFilter],
|
|
26
28
|
);
|
|
27
29
|
const divRef = useRef<HTMLDivElement>(null);
|
|
28
30
|
|
|
@@ -52,7 +54,7 @@ export function DownshiftCombobox<Item>({
|
|
|
52
54
|
closeMenu,
|
|
53
55
|
} = useCombobox({
|
|
54
56
|
onInputValueChange({ inputValue }) {
|
|
55
|
-
|
|
57
|
+
setItemsFilter(inputValue);
|
|
56
58
|
},
|
|
57
59
|
onSelectedItemChange({ selectedItem }) {
|
|
58
60
|
if (selectedItem !== null) {
|
|
@@ -97,6 +97,7 @@ function componentParametersToCode(componentName: string, params: object, lapisU
|
|
|
97
97
|
|
|
98
98
|
const attributes = indentLines(
|
|
99
99
|
Object.entries(params)
|
|
100
|
+
.filter(([_, value]) => value !== undefined)
|
|
100
101
|
.map(([key, value]) => `${key}='${stringifyIfNeeded(value) as string}'`)
|
|
101
102
|
.join('\n'),
|
|
102
103
|
4,
|
|
@@ -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,27 +38,14 @@ 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',
|
|
@@ -64,19 +53,17 @@ export const Default: StoryObj<MutationsOverTimeProps> = {
|
|
|
64
53
|
width: '100%',
|
|
65
54
|
granularity: 'month',
|
|
66
55
|
lapisDateField: 'date',
|
|
56
|
+
initialMeanProportionInterval: { min: 0.05, max: 0.9 },
|
|
67
57
|
},
|
|
68
58
|
};
|
|
69
59
|
|
|
70
60
|
// This test uses mock data: showMessagWhenTooManyMutations.ts (through mutationOverTimeWorker.mock.ts)
|
|
71
61
|
export const ShowsMessageWhenTooManyMutations: StoryObj<MutationsOverTimeProps> = {
|
|
72
|
-
...
|
|
62
|
+
...Default,
|
|
73
63
|
args: {
|
|
64
|
+
...Default.args,
|
|
74
65
|
lapisFilter: { dateFrom: '2023-01-01', dateTo: '2023-12-31' },
|
|
75
|
-
sequenceType: 'nucleotide',
|
|
76
|
-
views: ['grid'],
|
|
77
|
-
width: '100%',
|
|
78
66
|
granularity: 'year',
|
|
79
|
-
lapisDateField: 'date',
|
|
80
67
|
},
|
|
81
68
|
play: async ({ canvas }) => {
|
|
82
69
|
await waitFor(() => expect(canvas.getByText('Showing 100 of 137 mutations.', { exact: false })).toBeVisible(), {
|
|
@@ -86,15 +73,12 @@ export const ShowsMessageWhenTooManyMutations: StoryObj<MutationsOverTimeProps>
|
|
|
86
73
|
};
|
|
87
74
|
|
|
88
75
|
export const ShowsNoDataWhenNoMutationsAreInFilter: StoryObj<MutationsOverTimeProps> = {
|
|
89
|
-
...
|
|
76
|
+
...Default,
|
|
90
77
|
args: {
|
|
78
|
+
...Default.args,
|
|
91
79
|
lapisFilter: { dateFrom: '1800-01-01', dateTo: '1800-01-02' },
|
|
92
|
-
sequenceType: 'nucleotide',
|
|
93
|
-
views: ['grid'],
|
|
94
|
-
width: '100%',
|
|
95
80
|
height: '700px',
|
|
96
81
|
granularity: 'year',
|
|
97
|
-
lapisDateField: 'date',
|
|
98
82
|
},
|
|
99
83
|
play: async ({ canvas }) => {
|
|
100
84
|
await waitFor(() => expect(canvas.getByText('No data available.', { exact: false })).toBeVisible(), {
|
|
@@ -104,15 +88,12 @@ export const ShowsNoDataWhenNoMutationsAreInFilter: StoryObj<MutationsOverTimePr
|
|
|
104
88
|
};
|
|
105
89
|
|
|
106
90
|
export const ShowsNoDataMessageWhenThereAreNoDatesInFilter: StoryObj<MutationsOverTimeProps> = {
|
|
107
|
-
...
|
|
91
|
+
...Default,
|
|
108
92
|
args: {
|
|
93
|
+
...Default.args,
|
|
109
94
|
lapisFilter: { dateFrom: '2345-01-01', dateTo: '2020-01-02' },
|
|
110
|
-
sequenceType: 'nucleotide',
|
|
111
|
-
views: ['grid'],
|
|
112
|
-
width: '100%',
|
|
113
95
|
height: '700px',
|
|
114
96
|
granularity: 'year',
|
|
115
|
-
lapisDateField: 'date',
|
|
116
97
|
},
|
|
117
98
|
play: async ({ canvas }) => {
|
|
118
99
|
await waitFor(() => expect(canvas.getByText('No data available.', { exact: false })).toBeVisible(), {
|
|
@@ -122,15 +103,10 @@ export const ShowsNoDataMessageWhenThereAreNoDatesInFilter: StoryObj<MutationsOv
|
|
|
122
103
|
};
|
|
123
104
|
|
|
124
105
|
export const ShowsNoDataMessageForStrictFilters: StoryObj<MutationsOverTimeProps> = {
|
|
125
|
-
...
|
|
106
|
+
...Default,
|
|
126
107
|
args: {
|
|
108
|
+
...Default.args,
|
|
127
109
|
lapisFilter: { pangoLineage: 'JN.1*', dateFrom: '2024-01-15', dateTo: '2024-07-10' },
|
|
128
|
-
sequenceType: 'nucleotide',
|
|
129
|
-
views: ['grid'],
|
|
130
|
-
width: '100%',
|
|
131
|
-
height: '700px',
|
|
132
|
-
granularity: 'month',
|
|
133
|
-
lapisDateField: 'date',
|
|
134
110
|
},
|
|
135
111
|
play: async ({ canvas }) => {
|
|
136
112
|
await waitFor(() => expect(canvas.getByText('Grid')).toBeVisible(), { timeout: 10000 });
|
|
@@ -155,6 +131,19 @@ export const ShowsNoDataMessageForStrictFilters: StoryObj<MutationsOverTimeProps
|
|
|
155
131
|
},
|
|
156
132
|
};
|
|
157
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
|
+
|
|
158
147
|
export const WithNoLapisDateFieldField: StoryObj<MutationsOverTimeProps> = {
|
|
159
148
|
...Default,
|
|
160
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,6 +44,11 @@ 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
53
|
height: z.string().optional(),
|
|
49
54
|
});
|
|
@@ -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) {
|