@genspectrum/dashboard-components 0.13.0 → 0.13.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 +178 -0
- package/dist/assets/mutationOverTimeWorker-B1-WrM4b.js.map +1 -0
- package/dist/components.d.ts +69 -4
- package/dist/components.js +536 -286
- package/dist/components.js.map +1 -1
- package/dist/style.css +3 -0
- package/dist/util.d.ts +20 -4
- package/package.json +2 -2
- package/src/constants.ts +6 -0
- package/src/lapisApi/__mockData__/wiseReferenceGenome.json +9 -0
- package/src/lapisApi/lapisApi.ts +17 -0
- package/src/lapisApi/lapisTypes.ts +7 -1
- package/src/operator/FetchDetailsOperator.ts +28 -0
- package/src/preact/components/tabs.tsx +1 -1
- package/src/preact/mutationsOverTime/MutationOverTimeData.ts +9 -5
- package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +5 -3
- package/src/preact/shared/sort/sortSubstitutionsAndDeletions.ts +4 -7
- package/src/preact/wastewater/mutationsOverTime/__mockData__/details.json +88 -0
- package/src/preact/wastewater/mutationsOverTime/computeWastewaterMutationsOverTimeDataPerLocation.spec.ts +159 -0
- package/src/preact/wastewater/mutationsOverTime/computeWastewaterMutationsOverTimeDataPerLocation.ts +51 -0
- package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.stories.tsx +71 -0
- package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.tsx +151 -0
- package/src/query/queryMutationsOverTime.ts +6 -14
- package/src/query/queryWastewaterMutationsOverTime.spec.ts +94 -0
- package/src/query/queryWastewaterMutationsOverTime.ts +55 -0
- package/src/utils/map2d.ts +39 -0
- package/src/web-components/index.ts +1 -0
- package/src/web-components/wastewaterVisualization/gs-wastewater-mutations-over-time.stories.ts +82 -0
- package/src/web-components/wastewaterVisualization/gs-wastewater-mutations-over-time.tsx +112 -0
- package/src/web-components/wastewaterVisualization/index.ts +1 -0
- package/standalone-bundle/assets/{mutationOverTimeWorker-DEybsZ5r.js.map → mutationOverTimeWorker-Cls1J0cl.js.map} +1 -1
- package/standalone-bundle/dashboard-components.js +6203 -5996
- package/standalone-bundle/dashboard-components.js.map +1 -1
- package/standalone-bundle/style.css +1 -1
- package/dist/assets/mutationOverTimeWorker-DTv93Ere.js.map +0 -1
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { type FunctionComponent } from 'preact';
|
|
2
|
+
import { type Dispatch, type StateUpdater, useContext, useState } from 'preact/hooks';
|
|
3
|
+
import z from 'zod';
|
|
4
|
+
|
|
5
|
+
import { computeWastewaterMutationsOverTimeDataPerLocation } from './computeWastewaterMutationsOverTimeDataPerLocation';
|
|
6
|
+
import { lapisFilterSchema, sequenceTypeSchema } from '../../../types';
|
|
7
|
+
import { LapisUrlContext } from '../../LapisUrlContext';
|
|
8
|
+
import { type ColorScale } from '../../components/color-scale-selector';
|
|
9
|
+
import { ColorScaleSelectorDropdown } from '../../components/color-scale-selector-dropdown';
|
|
10
|
+
import { ErrorBoundary } from '../../components/error-boundary';
|
|
11
|
+
import { Fullscreen } from '../../components/fullscreen';
|
|
12
|
+
import Info, { InfoComponentCode, InfoHeadline1, InfoParagraph } from '../../components/info';
|
|
13
|
+
import { LoadingDisplay } from '../../components/loading-display';
|
|
14
|
+
import { NoDataDisplay } from '../../components/no-data-display';
|
|
15
|
+
import { ResizeContainer } from '../../components/resize-container';
|
|
16
|
+
import Tabs from '../../components/tabs';
|
|
17
|
+
import { type MutationOverTimeDataMap } from '../../mutationsOverTime/MutationOverTimeData';
|
|
18
|
+
import MutationsOverTimeGrid from '../../mutationsOverTime/mutations-over-time-grid';
|
|
19
|
+
import { useQuery } from '../../useQuery';
|
|
20
|
+
|
|
21
|
+
const wastewaterMutationOverTimeSchema = z.object({
|
|
22
|
+
lapisFilter: lapisFilterSchema,
|
|
23
|
+
sequenceType: sequenceTypeSchema,
|
|
24
|
+
width: z.string(),
|
|
25
|
+
height: z.string(),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export type WastewaterMutationsOverTimeProps = z.infer<typeof wastewaterMutationOverTimeSchema>;
|
|
29
|
+
|
|
30
|
+
export const WastewaterMutationsOverTime: FunctionComponent<WastewaterMutationsOverTimeProps> = (componentProps) => {
|
|
31
|
+
const { width, height } = componentProps;
|
|
32
|
+
const size = { height, width };
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<ErrorBoundary size={size} schema={wastewaterMutationOverTimeSchema} componentProps={componentProps}>
|
|
36
|
+
<ResizeContainer size={size}>
|
|
37
|
+
<WastewaterMutationsOverTimeInner {...componentProps} />
|
|
38
|
+
</ResizeContainer>
|
|
39
|
+
</ErrorBoundary>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const WastewaterMutationsOverTimeInner: FunctionComponent<WastewaterMutationsOverTimeProps> = (
|
|
44
|
+
componentProps,
|
|
45
|
+
) => {
|
|
46
|
+
const lapis = useContext(LapisUrlContext);
|
|
47
|
+
|
|
48
|
+
const {
|
|
49
|
+
data: mutationOverTimeDataPerLocation,
|
|
50
|
+
error,
|
|
51
|
+
isLoading,
|
|
52
|
+
} = useQuery(
|
|
53
|
+
() =>
|
|
54
|
+
computeWastewaterMutationsOverTimeDataPerLocation(
|
|
55
|
+
lapis,
|
|
56
|
+
componentProps.lapisFilter,
|
|
57
|
+
componentProps.sequenceType,
|
|
58
|
+
),
|
|
59
|
+
[],
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
if (isLoading) {
|
|
63
|
+
return <LoadingDisplay />;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (error !== null) {
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (mutationOverTimeDataPerLocation.length === 0) {
|
|
71
|
+
return <NoDataDisplay />;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<MutationsOverTimeTabs
|
|
76
|
+
mutationOverTimeDataPerLocation={mutationOverTimeDataPerLocation}
|
|
77
|
+
originalComponentProps={componentProps}
|
|
78
|
+
/>
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
type MutationOverTimeDataPerLocation = {
|
|
83
|
+
location: string;
|
|
84
|
+
data: MutationOverTimeDataMap;
|
|
85
|
+
}[];
|
|
86
|
+
|
|
87
|
+
type MutationOverTimeTabsProps = {
|
|
88
|
+
mutationOverTimeDataPerLocation: MutationOverTimeDataPerLocation;
|
|
89
|
+
originalComponentProps: WastewaterMutationsOverTimeProps;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
|
|
93
|
+
mutationOverTimeDataPerLocation,
|
|
94
|
+
originalComponentProps,
|
|
95
|
+
}) => {
|
|
96
|
+
const [colorScale, setColorScale] = useState<ColorScale>({ min: 0, max: 1, color: 'indigo' });
|
|
97
|
+
|
|
98
|
+
const tabs = mutationOverTimeDataPerLocation.map(({ location, data }) => ({
|
|
99
|
+
title: location,
|
|
100
|
+
content: <MutationsOverTimeGrid data={data} colorScale={colorScale} />,
|
|
101
|
+
}));
|
|
102
|
+
|
|
103
|
+
const toolbar = (
|
|
104
|
+
<Toolbar
|
|
105
|
+
colorScale={colorScale}
|
|
106
|
+
setColorScale={setColorScale}
|
|
107
|
+
originalComponentProps={originalComponentProps}
|
|
108
|
+
data={mutationOverTimeDataPerLocation}
|
|
109
|
+
/>
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
return <Tabs tabs={tabs} toolbar={toolbar} />;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
type ToolbarProps = {
|
|
116
|
+
colorScale: ColorScale;
|
|
117
|
+
setColorScale: Dispatch<StateUpdater<ColorScale>>;
|
|
118
|
+
originalComponentProps: WastewaterMutationsOverTimeProps;
|
|
119
|
+
data: MutationOverTimeDataPerLocation;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const Toolbar: FunctionComponent<ToolbarProps> = ({ colorScale, setColorScale, originalComponentProps }) => {
|
|
123
|
+
return (
|
|
124
|
+
<>
|
|
125
|
+
<ColorScaleSelectorDropdown colorScale={colorScale} setColorScale={setColorScale} />
|
|
126
|
+
<WastewaterMutationsOverTimeInfo originalComponentProps={originalComponentProps} />
|
|
127
|
+
<Fullscreen />
|
|
128
|
+
</>
|
|
129
|
+
);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
type WastewaterMutationsOverTimeInfoProps = {
|
|
133
|
+
originalComponentProps: WastewaterMutationsOverTimeProps;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const WastewaterMutationsOverTimeInfo: FunctionComponent<WastewaterMutationsOverTimeInfoProps> = ({
|
|
137
|
+
originalComponentProps,
|
|
138
|
+
}) => {
|
|
139
|
+
const lapis = useContext(LapisUrlContext);
|
|
140
|
+
return (
|
|
141
|
+
<Info>
|
|
142
|
+
<InfoHeadline1>Info for mutations over time</InfoHeadline1>
|
|
143
|
+
<InfoParagraph> </InfoParagraph>
|
|
144
|
+
<InfoComponentCode
|
|
145
|
+
componentName='wastewater-mutations-over-time'
|
|
146
|
+
params={originalComponentProps}
|
|
147
|
+
lapisUrl={lapis}
|
|
148
|
+
/>
|
|
149
|
+
</Info>
|
|
150
|
+
);
|
|
151
|
+
};
|
|
@@ -16,14 +16,7 @@ import {
|
|
|
16
16
|
type SubstitutionOrDeletionEntry,
|
|
17
17
|
type TemporalGranularity,
|
|
18
18
|
} from '../types';
|
|
19
|
-
import { type
|
|
20
|
-
import {
|
|
21
|
-
type Deletion,
|
|
22
|
-
type DeletionClass,
|
|
23
|
-
type Substitution,
|
|
24
|
-
type SubstitutionClass,
|
|
25
|
-
toSubstitutionOrDeletion,
|
|
26
|
-
} from '../utils/mutations';
|
|
19
|
+
import { type Deletion, type Substitution, toSubstitutionOrDeletion } from '../utils/mutations';
|
|
27
20
|
import {
|
|
28
21
|
compareTemporal,
|
|
29
22
|
dateRangeCompare,
|
|
@@ -41,12 +34,11 @@ export type MutationOverTimeData = {
|
|
|
41
34
|
totalCount: number;
|
|
42
35
|
};
|
|
43
36
|
|
|
44
|
-
export type MutationOverTimeMutationValue = {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
>;
|
|
37
|
+
export type MutationOverTimeMutationValue = {
|
|
38
|
+
proportion: number;
|
|
39
|
+
count: number | null;
|
|
40
|
+
totalCount: number | null;
|
|
41
|
+
} | null;
|
|
50
42
|
|
|
51
43
|
const MAX_NUMBER_OF_GRID_COLUMNS = 200;
|
|
52
44
|
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { queryWastewaterMutationsOverTime } from './queryWastewaterMutationsOverTime';
|
|
4
|
+
import { DUMMY_LAPIS_URL, lapisRequestMocks } from '../../vitest.setup';
|
|
5
|
+
import { SubstitutionClass } from '../utils/mutations';
|
|
6
|
+
import { TemporalCache } from '../utils/temporalClass';
|
|
7
|
+
|
|
8
|
+
const temporalCache = TemporalCache.getInstance();
|
|
9
|
+
|
|
10
|
+
describe('queryWastewaterMutationsOverTime', () => {
|
|
11
|
+
it('should fetch data', async () => {
|
|
12
|
+
const lapisFilter = { country: 'Germany' };
|
|
13
|
+
|
|
14
|
+
lapisRequestMocks.details(
|
|
15
|
+
{
|
|
16
|
+
country: 'Germany',
|
|
17
|
+
fields: ['date', 'location', 'nucleotideMutationFrequency', 'aminoAcidMutationFrequency'],
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
data: [
|
|
21
|
+
{
|
|
22
|
+
date: '2021-01-01',
|
|
23
|
+
location: 'Germany',
|
|
24
|
+
reference: 'organismA',
|
|
25
|
+
nucleotideMutationFrequency: JSON.stringify({
|
|
26
|
+
A123T: 0.4,
|
|
27
|
+
'123G': null,
|
|
28
|
+
}),
|
|
29
|
+
aminoAcidMutationFrequency: null,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
date: '2021-01-02',
|
|
33
|
+
location: 'Germany',
|
|
34
|
+
reference: 'organismA',
|
|
35
|
+
nucleotideMutationFrequency: null,
|
|
36
|
+
aminoAcidMutationFrequency: JSON.stringify({
|
|
37
|
+
'S:A123T': 0.4,
|
|
38
|
+
'S:123G': 0.1,
|
|
39
|
+
}),
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const result = await queryWastewaterMutationsOverTime(DUMMY_LAPIS_URL, lapisFilter);
|
|
46
|
+
|
|
47
|
+
expect(result).to.deep.equal([
|
|
48
|
+
{
|
|
49
|
+
location: 'Germany',
|
|
50
|
+
date: temporalCache.getYearMonthDay('2021-01-01'),
|
|
51
|
+
nucleotideMutationFrequency: [
|
|
52
|
+
{ mutation: SubstitutionClass.parse('A123T'), proportion: 0.4 },
|
|
53
|
+
{ mutation: SubstitutionClass.parse('123G'), proportion: null },
|
|
54
|
+
],
|
|
55
|
+
aminoAcidMutationFrequency: [],
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
location: 'Germany',
|
|
59
|
+
date: temporalCache.getYearMonthDay('2021-01-02'),
|
|
60
|
+
nucleotideMutationFrequency: [],
|
|
61
|
+
aminoAcidMutationFrequency: [
|
|
62
|
+
{ mutation: SubstitutionClass.parse('S:A123T'), proportion: 0.4 },
|
|
63
|
+
{ mutation: SubstitutionClass.parse('S:123G'), proportion: 0.1 },
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should error when the mutation frequency object is invalid', async () => {
|
|
70
|
+
const lapisFilter = { country: 'Germany' };
|
|
71
|
+
|
|
72
|
+
lapisRequestMocks.details(
|
|
73
|
+
{
|
|
74
|
+
country: 'Germany',
|
|
75
|
+
fields: ['date', 'location', 'nucleotideMutationFrequency', 'aminoAcidMutationFrequency'],
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
data: [
|
|
79
|
+
{
|
|
80
|
+
date: '2021-01-01',
|
|
81
|
+
location: 'Germany',
|
|
82
|
+
reference: 'organismA',
|
|
83
|
+
nucleotideMutationFrequency: JSON.stringify({ key: 'not an object of the expected type' }),
|
|
84
|
+
aminoAcidMutationFrequency: null,
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
await expect(queryWastewaterMutationsOverTime(DUMMY_LAPIS_URL, lapisFilter)).rejects.toThrowError(
|
|
91
|
+
/^Failed to parse mutation frequency/,
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import z from 'zod';
|
|
2
|
+
|
|
3
|
+
import { FetchDetailsOperator } from '../operator/FetchDetailsOperator';
|
|
4
|
+
import { type LapisFilter } from '../types';
|
|
5
|
+
import { type Substitution, SubstitutionClass } from '../utils/mutations';
|
|
6
|
+
import { parseDateStringToTemporal, type TemporalClass, toTemporalClass } from '../utils/temporalClass';
|
|
7
|
+
|
|
8
|
+
export type WastewaterData = {
|
|
9
|
+
location: string;
|
|
10
|
+
date: TemporalClass;
|
|
11
|
+
nucleotideMutationFrequency: { mutation: Substitution; proportion: number | null }[];
|
|
12
|
+
aminoAcidMutationFrequency: { mutation: Substitution; proportion: number | null }[];
|
|
13
|
+
}[];
|
|
14
|
+
|
|
15
|
+
export async function queryWastewaterMutationsOverTime(
|
|
16
|
+
lapis: string,
|
|
17
|
+
lapisFilter: LapisFilter,
|
|
18
|
+
signal?: AbortSignal,
|
|
19
|
+
): Promise<WastewaterData> {
|
|
20
|
+
const fetchData = new FetchDetailsOperator(lapisFilter, [
|
|
21
|
+
'date',
|
|
22
|
+
'location',
|
|
23
|
+
'nucleotideMutationFrequency',
|
|
24
|
+
'aminoAcidMutationFrequency',
|
|
25
|
+
]);
|
|
26
|
+
const data = (await fetchData.evaluate(lapis, signal)).content;
|
|
27
|
+
|
|
28
|
+
return data.map((row) => ({
|
|
29
|
+
location: row.location as string,
|
|
30
|
+
date: toTemporalClass(parseDateStringToTemporal(row.date as string, 'day')),
|
|
31
|
+
nucleotideMutationFrequency:
|
|
32
|
+
row.nucleotideMutationFrequency !== null
|
|
33
|
+
? transformMutations(JSON.parse(row.nucleotideMutationFrequency as string))
|
|
34
|
+
: [],
|
|
35
|
+
aminoAcidMutationFrequency:
|
|
36
|
+
row.aminoAcidMutationFrequency !== null
|
|
37
|
+
? transformMutations(JSON.parse(row.aminoAcidMutationFrequency as string))
|
|
38
|
+
: [],
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const mutationFrequencySchema = z.record(z.number().nullable());
|
|
43
|
+
|
|
44
|
+
function transformMutations(input: unknown): { mutation: Substitution; proportion: number | null }[] {
|
|
45
|
+
const mutationFrequency = mutationFrequencySchema.safeParse(input);
|
|
46
|
+
|
|
47
|
+
if (!mutationFrequency.success) {
|
|
48
|
+
throw new Error(`Failed to parse mutation frequency: ${mutationFrequency.error.message}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return Object.entries(mutationFrequency.data).map(([key, value]) => ({
|
|
52
|
+
mutation: SubstitutionClass.parse(key)!,
|
|
53
|
+
proportion: value,
|
|
54
|
+
}));
|
|
55
|
+
}
|
package/src/utils/map2d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export interface Map2d<Key1, Key2, Value> {
|
|
|
17
17
|
|
|
18
18
|
serializeSecondAxis(key: Key2): string;
|
|
19
19
|
|
|
20
|
+
getContents(): Map2DContents<Key1, Key2, Value>;
|
|
21
|
+
|
|
20
22
|
readonly keysFirstAxis: Map<string, Key1>;
|
|
21
23
|
readonly keysSecondAxis: Map<string, Key2>;
|
|
22
24
|
}
|
|
@@ -106,6 +108,35 @@ export class Map2dBase<Key1 extends object | string, Key2 extends object | strin
|
|
|
106
108
|
}
|
|
107
109
|
}
|
|
108
110
|
|
|
111
|
+
export class SortedMap2d<Key1 extends object | string, Key2 extends object | string, Value> extends Map2dBase<
|
|
112
|
+
Key1,
|
|
113
|
+
Key2,
|
|
114
|
+
Value
|
|
115
|
+
> {
|
|
116
|
+
constructor(
|
|
117
|
+
delegate: Map2d<Key1, Key2, Value>,
|
|
118
|
+
sortFirstAxis: (a: Key1, b: Key1) => number,
|
|
119
|
+
sortSecondAxis: (a: Key2, b: Key2) => number,
|
|
120
|
+
) {
|
|
121
|
+
const contents = delegate.getContents();
|
|
122
|
+
const sortedFirstAxisKeys = new Map(
|
|
123
|
+
[...contents.keysFirstAxis.entries()].sort((a, b) => sortFirstAxis(a[1], b[1])),
|
|
124
|
+
);
|
|
125
|
+
const sortedSecondAxisKeys = new Map(
|
|
126
|
+
[...contents.keysSecondAxis.entries()].sort((a, b) => sortSecondAxis(a[1], b[1])),
|
|
127
|
+
);
|
|
128
|
+
super(
|
|
129
|
+
(key: Key1) => delegate.serializeFirstAxis(key),
|
|
130
|
+
(key: Key2) => delegate.serializeSecondAxis(key),
|
|
131
|
+
{
|
|
132
|
+
keysFirstAxis: sortedFirstAxisKeys,
|
|
133
|
+
keysSecondAxis: sortedSecondAxisKeys,
|
|
134
|
+
data: contents.data,
|
|
135
|
+
},
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
109
140
|
export class Map2dView<Key1 extends object | string, Key2 extends object | string, Value>
|
|
110
141
|
implements Map2d<Key1, Key2, Value>
|
|
111
142
|
{
|
|
@@ -175,4 +206,12 @@ export class Map2dView<Key1 extends object | string, Key2 extends object | strin
|
|
|
175
206
|
|
|
176
207
|
return this.baseMap.getRow(key);
|
|
177
208
|
}
|
|
209
|
+
|
|
210
|
+
getContents() {
|
|
211
|
+
return {
|
|
212
|
+
keysFirstAxis: this.keysFirstAxis,
|
|
213
|
+
keysSecondAxis: this.keysSecondAxis,
|
|
214
|
+
data: this.baseMap.getContents().data,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
178
217
|
}
|
package/src/web-components/wastewaterVisualization/gs-wastewater-mutations-over-time.stories.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/web-components';
|
|
2
|
+
import { html } from 'lit';
|
|
3
|
+
|
|
4
|
+
import './gs-wastewater-mutations-over-time';
|
|
5
|
+
import '../app';
|
|
6
|
+
import { withComponentDocs } from '../../../.storybook/ComponentDocsBlock';
|
|
7
|
+
import { WISE_DETAILS_ENDPOINT, WISE_LAPIS_URL } from '../../constants';
|
|
8
|
+
import details from '../../preact/wastewater/mutationsOverTime/__mockData__/details.json';
|
|
9
|
+
import { type WastewaterMutationsOverTimeProps } from '../../preact/wastewater/mutationsOverTime/wastewater-mutations-over-time';
|
|
10
|
+
|
|
11
|
+
const codeExample = String.raw`
|
|
12
|
+
<gs-wastewater-mutations-over-time
|
|
13
|
+
lapisFilter='{ "dateFrom": "2024-01-01" }'
|
|
14
|
+
sequenceType='nucleotide'
|
|
15
|
+
width='100%'
|
|
16
|
+
height='700px'
|
|
17
|
+
></gs-wastewater-mutations-over-time>`;
|
|
18
|
+
|
|
19
|
+
const meta: Meta<Required<WastewaterMutationsOverTimeProps>> = {
|
|
20
|
+
title: 'Wastewater visualization/Wastewater mutations over time',
|
|
21
|
+
component: 'gs-wastewater-mutations-over-time',
|
|
22
|
+
argTypes: {
|
|
23
|
+
lapisFilter: { control: 'object' },
|
|
24
|
+
sequenceType: {
|
|
25
|
+
options: ['nucleotide', 'amino acid'],
|
|
26
|
+
control: { type: 'radio' },
|
|
27
|
+
},
|
|
28
|
+
width: { control: 'text' },
|
|
29
|
+
height: { control: 'text' },
|
|
30
|
+
},
|
|
31
|
+
args: {
|
|
32
|
+
lapisFilter: { versionStatus: 'LATEST_VERSION', isRevocation: false },
|
|
33
|
+
sequenceType: 'nucleotide',
|
|
34
|
+
width: '100%',
|
|
35
|
+
height: '700px',
|
|
36
|
+
},
|
|
37
|
+
parameters: withComponentDocs({
|
|
38
|
+
componentDocs: {
|
|
39
|
+
opensShadowDom: true,
|
|
40
|
+
expectsChildren: false,
|
|
41
|
+
codeExample,
|
|
42
|
+
},
|
|
43
|
+
fetchMock: {},
|
|
44
|
+
}),
|
|
45
|
+
tags: ['autodocs'],
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export default meta;
|
|
49
|
+
|
|
50
|
+
export const WastewaterMutationsOverTime: StoryObj<Required<WastewaterMutationsOverTimeProps>> = {
|
|
51
|
+
render: (args) => html`
|
|
52
|
+
<gs-app lapis="${WISE_LAPIS_URL}">
|
|
53
|
+
<gs-wastewater-mutations-over-time
|
|
54
|
+
.lapisFilter=${args.lapisFilter}
|
|
55
|
+
.sequenceType=${args.sequenceType}
|
|
56
|
+
.width=${args.width}
|
|
57
|
+
.height=${args.height}
|
|
58
|
+
></gs-wastewater-mutations-over-time>
|
|
59
|
+
</gs-app>
|
|
60
|
+
`,
|
|
61
|
+
parameters: {
|
|
62
|
+
fetchMock: {
|
|
63
|
+
mocks: [
|
|
64
|
+
{
|
|
65
|
+
matcher: {
|
|
66
|
+
name: 'details',
|
|
67
|
+
url: WISE_DETAILS_ENDPOINT,
|
|
68
|
+
body: {
|
|
69
|
+
fields: ['date', 'location', 'nucleotideMutationFrequency', 'aminoAcidMutationFrequency'],
|
|
70
|
+
versionStatus: 'LATEST_VERSION',
|
|
71
|
+
isRevocation: false,
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
response: {
|
|
75
|
+
status: 200,
|
|
76
|
+
body: details,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { customElement, property } from 'lit/decorators.js';
|
|
2
|
+
import { type DetailedHTMLProps, type HTMLAttributes } from 'react';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
WastewaterMutationsOverTime,
|
|
6
|
+
type WastewaterMutationsOverTimeProps,
|
|
7
|
+
} from '../../preact/wastewater/mutationsOverTime/wastewater-mutations-over-time';
|
|
8
|
+
import { type Equals, type Expect } from '../../utils/typeAssertions';
|
|
9
|
+
import { PreactLitAdapterWithGridJsStyles } from '../PreactLitAdapterWithGridJsStyles';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* ## Context
|
|
13
|
+
*
|
|
14
|
+
* This component displays mutations for Swiss wastewater data generated within the WISE consortium. It is designed
|
|
15
|
+
* only for this purpose and is not designed to be reused outside the WISE project.
|
|
16
|
+
*
|
|
17
|
+
* It relies on a LAPIS instance that has the fields `nucleotideMutationFrequency` and `aminoAcidMutationFrequency`.
|
|
18
|
+
* Those fields are expected to be JSON strings of the format `{ [mutation]: frequency | null }`
|
|
19
|
+
* (e.g. `{ "A123T": 0.5, "C456G": 0.7, "T789G": null }`).
|
|
20
|
+
*
|
|
21
|
+
* The component will stratify by `location`.
|
|
22
|
+
* Every location will be rendered in a separate tab.
|
|
23
|
+
* The content of the tab is a "mutations over time" grid, similar to the one used in the `gs-mutations-over-time` component.
|
|
24
|
+
*
|
|
25
|
+
* This component also assumes that the LAPIS instance has the field `date` which can be used for the time axis.
|
|
26
|
+
*/
|
|
27
|
+
@customElement('gs-wastewater-mutations-over-time')
|
|
28
|
+
export class WastewaterMutationsOverTimeComponent extends PreactLitAdapterWithGridJsStyles {
|
|
29
|
+
/**
|
|
30
|
+
* Required.
|
|
31
|
+
*
|
|
32
|
+
* LAPIS filter to select the displayed data.
|
|
33
|
+
*/
|
|
34
|
+
@property({ type: Object })
|
|
35
|
+
lapisFilter: Record<string, string | string[] | number | null | boolean | undefined> & {
|
|
36
|
+
nucleotideMutations?: string[];
|
|
37
|
+
aminoAcidMutations?: string[];
|
|
38
|
+
nucleotideInsertions?: string[];
|
|
39
|
+
aminoAcidInsertions?: string[];
|
|
40
|
+
} = {};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Required.
|
|
44
|
+
*
|
|
45
|
+
* Whether to display nucleotide or amino acid mutations.
|
|
46
|
+
*/
|
|
47
|
+
@property({ type: String })
|
|
48
|
+
sequenceType: 'nucleotide' | 'amino acid' = 'nucleotide';
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The width of the component.
|
|
52
|
+
*
|
|
53
|
+
* Visit https://genspectrum.github.io/dashboard-components/?path=/docs/components-size-of-components--docs for more information.
|
|
54
|
+
*/
|
|
55
|
+
@property({ type: String })
|
|
56
|
+
width: string = '100%';
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The height of the component.
|
|
60
|
+
*
|
|
61
|
+
* Visit https://genspectrum.github.io/dashboard-components/?path=/docs/components-size-of-components--docs for more information.
|
|
62
|
+
*/
|
|
63
|
+
@property({ type: String })
|
|
64
|
+
height: string = '700px';
|
|
65
|
+
|
|
66
|
+
override render() {
|
|
67
|
+
return (
|
|
68
|
+
<WastewaterMutationsOverTime
|
|
69
|
+
lapisFilter={this.lapisFilter}
|
|
70
|
+
sequenceType={this.sequenceType}
|
|
71
|
+
width={this.width}
|
|
72
|
+
height={this.height}
|
|
73
|
+
/>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
declare global {
|
|
79
|
+
interface HTMLElementTagNameMap {
|
|
80
|
+
'gs-wastewater-mutations-over-time': WastewaterMutationsOverTimeComponent;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
declare global {
|
|
85
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
86
|
+
namespace JSX {
|
|
87
|
+
interface IntrinsicElements {
|
|
88
|
+
'gs-wastewater-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* eslint-disable @typescript-eslint/no-unused-vars, no-unused-vars */
|
|
94
|
+
type LapisFilterMatches = Expect<
|
|
95
|
+
Equals<
|
|
96
|
+
typeof WastewaterMutationsOverTimeComponent.prototype.lapisFilter,
|
|
97
|
+
WastewaterMutationsOverTimeProps['lapisFilter']
|
|
98
|
+
>
|
|
99
|
+
>;
|
|
100
|
+
type SequenceTypeMatches = Expect<
|
|
101
|
+
Equals<
|
|
102
|
+
typeof WastewaterMutationsOverTimeComponent.prototype.sequenceType,
|
|
103
|
+
WastewaterMutationsOverTimeProps['sequenceType']
|
|
104
|
+
>
|
|
105
|
+
>;
|
|
106
|
+
type WidthMatches = Expect<
|
|
107
|
+
Equals<typeof WastewaterMutationsOverTimeComponent.prototype.width, WastewaterMutationsOverTimeProps['width']>
|
|
108
|
+
>;
|
|
109
|
+
type HeightMatches = Expect<
|
|
110
|
+
Equals<typeof WastewaterMutationsOverTimeComponent.prototype.height, WastewaterMutationsOverTimeProps['height']>
|
|
111
|
+
>;
|
|
112
|
+
/* eslint-enable @typescript-eslint/no-unused-vars, no-unused-vars */
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { WastewaterMutationsOverTimeComponent } from './gs-wastewater-mutations-over-time';
|