@genspectrum/dashboard-components 0.3.0 → 0.3.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 +43 -5
- package/dist/dashboard-components.js +62 -87
- package/dist/dashboard-components.js.map +1 -1
- package/dist/genspectrum-components.d.ts +17 -1
- package/dist/style.css +0 -46
- package/package.json +1 -1
- package/src/operator/RenameFieldOperator.spec.ts +28 -0
- package/src/operator/RenameFieldOperator.ts +19 -0
- package/src/preact/locationFilter/location-filter.stories.tsx +1 -1
- package/src/preact/locationFilter/location-filter.tsx +12 -25
- package/src/preact/prevalenceOverTime/prevalence-over-time.stories.tsx +3 -0
- package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +5 -1
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.stories.tsx +2 -0
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +5 -1
- package/src/query/queryPrevalenceOverTime.ts +13 -7
- package/src/query/queryRelativeGrowthAdvantage.ts +11 -7
- package/src/web-components/input/gs-location-filter.stories.ts +0 -4
- package/src/web-components/input/gs-location-filter.tsx +1 -1
- package/src/web-components/visualization/gs-prevalence-over-time.stories.ts +6 -2
- package/src/web-components/visualization/gs-prevalence-over-time.tsx +11 -0
- package/src/web-components/visualization/gs-relative-growth-advantage.stories.ts +3 -0
- package/src/web-components/visualization/gs-relative-growth-advantage.tsx +11 -0
|
@@ -171,7 +171,7 @@ declare type LapisFilter = Record<string, string | number | null | boolean>;
|
|
|
171
171
|
* Values for the fields `i > K` are considered `undefined`.
|
|
172
172
|
*
|
|
173
173
|
* @fires {CustomEvent<Record<string, string>>} gs-location-changed
|
|
174
|
-
* Fired when the
|
|
174
|
+
* Fired when a value from the datalist is selected or when a valid value is typed into the field.
|
|
175
175
|
* The `details` of this event contain an object with all `fields` as keys
|
|
176
176
|
* and the corresponding values as values, if they are not `undefined`.
|
|
177
177
|
* Example:
|
|
@@ -529,6 +529,14 @@ export declare class PrevalenceOverTimeComponent extends PreactLitAdapterWithGri
|
|
|
529
529
|
* Visit https://genspectrum.github.io/dashboards/?path=/docs/components-size-of-components--docs for more information.
|
|
530
530
|
*/
|
|
531
531
|
height: string;
|
|
532
|
+
/**
|
|
533
|
+
* Required.
|
|
534
|
+
*
|
|
535
|
+
* The LAPIS field that the data should be aggregated by.
|
|
536
|
+
* The values will be used on the x-axis of the diagram.
|
|
537
|
+
* Must be a field of type `date` in LAPIS.
|
|
538
|
+
*/
|
|
539
|
+
lapisDateField: string;
|
|
532
540
|
render(): JSX_2.Element;
|
|
533
541
|
}
|
|
534
542
|
|
|
@@ -639,6 +647,14 @@ export declare class RelativeGrowthAdvantageComponent extends PreactLitAdapter {
|
|
|
639
647
|
* Visit https://genspectrum.github.io/dashboards/?path=/docs/components-size-of-components--docs for more information.
|
|
640
648
|
*/
|
|
641
649
|
height: string;
|
|
650
|
+
/**
|
|
651
|
+
* Required.
|
|
652
|
+
*
|
|
653
|
+
* The LAPIS field that the data should be aggregated by.
|
|
654
|
+
* The values will be used on the x-axis of the diagram.
|
|
655
|
+
* Must be a field of type `date` in LAPIS.
|
|
656
|
+
*/
|
|
657
|
+
lapisDateField: string;
|
|
642
658
|
render(): JSX_2.Element;
|
|
643
659
|
}
|
|
644
660
|
|
package/dist/style.css
CHANGED
|
@@ -1246,19 +1246,6 @@ html {
|
|
|
1246
1246
|
}
|
|
1247
1247
|
}
|
|
1248
1248
|
|
|
1249
|
-
.btn-outline.btn-primary:hover {
|
|
1250
|
-
--tw-text-opacity: 1;
|
|
1251
|
-
color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)));
|
|
1252
|
-
}
|
|
1253
|
-
|
|
1254
|
-
@supports (color: color-mix(in oklab, black, black)) {
|
|
1255
|
-
|
|
1256
|
-
.btn-outline.btn-primary:hover {
|
|
1257
|
-
background-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black);
|
|
1258
|
-
border-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black);
|
|
1259
|
-
}
|
|
1260
|
-
}
|
|
1261
|
-
|
|
1262
1249
|
.btn-disabled:hover,
|
|
1263
1250
|
.btn[disabled]:hover,
|
|
1264
1251
|
.btn:disabled:hover {
|
|
@@ -1728,34 +1715,12 @@ input.tab:checked + .tab-content,
|
|
|
1728
1715
|
background-color: var(--btn-color, var(--fallback-b2));
|
|
1729
1716
|
border-color: var(--btn-color, var(--fallback-b2));
|
|
1730
1717
|
}
|
|
1731
|
-
|
|
1732
|
-
.btn-primary {
|
|
1733
|
-
--btn-color: var(--fallback-p);
|
|
1734
|
-
}
|
|
1735
|
-
}
|
|
1736
|
-
@supports (color: color-mix(in oklab, black, black)) {
|
|
1737
|
-
|
|
1738
|
-
.btn-outline.btn-primary.btn-active {
|
|
1739
|
-
background-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black);
|
|
1740
|
-
border-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black);
|
|
1741
|
-
}
|
|
1742
1718
|
}
|
|
1743
1719
|
.btn:focus-visible {
|
|
1744
1720
|
outline-style: solid;
|
|
1745
1721
|
outline-width: 2px;
|
|
1746
1722
|
outline-offset: 2px;
|
|
1747
1723
|
}
|
|
1748
|
-
.btn-primary {
|
|
1749
|
-
--tw-text-opacity: 1;
|
|
1750
|
-
color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)));
|
|
1751
|
-
outline-color: var(--fallback-p,oklch(var(--p)/1));
|
|
1752
|
-
}
|
|
1753
|
-
@supports (color: oklch(0% 0 0)) {
|
|
1754
|
-
|
|
1755
|
-
.btn-primary {
|
|
1756
|
-
--btn-color: var(--p);
|
|
1757
|
-
}
|
|
1758
|
-
}
|
|
1759
1724
|
.btn.glass {
|
|
1760
1725
|
--tw-shadow: 0 0 #0000;
|
|
1761
1726
|
--tw-shadow-colored: 0 0 #0000;
|
|
@@ -1780,14 +1745,6 @@ input.tab:checked + .tab-content,
|
|
|
1780
1745
|
border-color: transparent;
|
|
1781
1746
|
background-color: var(--fallback-bc,oklch(var(--bc)/0.2));
|
|
1782
1747
|
}
|
|
1783
|
-
.btn-outline.btn-primary {
|
|
1784
|
-
--tw-text-opacity: 1;
|
|
1785
|
-
color: var(--fallback-p,oklch(var(--p)/var(--tw-text-opacity)));
|
|
1786
|
-
}
|
|
1787
|
-
.btn-outline.btn-primary.btn-active {
|
|
1788
|
-
--tw-text-opacity: 1;
|
|
1789
|
-
color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)));
|
|
1790
|
-
}
|
|
1791
1748
|
.btn.btn-disabled,
|
|
1792
1749
|
.btn[disabled],
|
|
1793
1750
|
.btn:disabled {
|
|
@@ -2938,9 +2895,6 @@ input.tab:checked + .tab-content,
|
|
|
2938
2895
|
.me-1 {
|
|
2939
2896
|
margin-inline-end: 0.25rem;
|
|
2940
2897
|
}
|
|
2941
|
-
.ml-1 {
|
|
2942
|
-
margin-left: 0.25rem;
|
|
2943
|
-
}
|
|
2944
2898
|
.ml-2 {
|
|
2945
2899
|
margin-left: 0.5rem;
|
|
2946
2900
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { MockOperator } from './MockOperator';
|
|
4
|
+
import { RenameFieldOperator } from './RenameFieldOperator';
|
|
5
|
+
|
|
6
|
+
const mockOperator = new MockOperator([
|
|
7
|
+
{ id: 1, value: 1 },
|
|
8
|
+
{ id: 2, value: 2 },
|
|
9
|
+
{ id: 3, value: 3 },
|
|
10
|
+
{ id: 4, value: 4 },
|
|
11
|
+
{ id: 5, value: 5 },
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
describe('RenameFieldOperator', () => {
|
|
15
|
+
it('should map the keys', async () => {
|
|
16
|
+
const underTest = new RenameFieldOperator(mockOperator, 'value', 'mappedValue');
|
|
17
|
+
|
|
18
|
+
const result = await underTest.evaluate('lapis');
|
|
19
|
+
|
|
20
|
+
expect(result.content).deep.equal([
|
|
21
|
+
{ id: 1, value: 1, mappedValue: 1 },
|
|
22
|
+
{ id: 2, value: 2, mappedValue: 2 },
|
|
23
|
+
{ id: 3, value: 3, mappedValue: 3 },
|
|
24
|
+
{ id: 4, value: 4, mappedValue: 4 },
|
|
25
|
+
{ id: 5, value: 5, mappedValue: 5 },
|
|
26
|
+
]);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { MapOperator } from './MapOperator';
|
|
2
|
+
import type { Operator } from './Operator';
|
|
3
|
+
|
|
4
|
+
export class RenameFieldOperator<
|
|
5
|
+
OldFieldName extends string,
|
|
6
|
+
NewFieldName extends string,
|
|
7
|
+
Data extends { [key in OldFieldName]: unknown },
|
|
8
|
+
> extends MapOperator<Data, Data & { [key in NewFieldName]: Data[OldFieldName] }> {
|
|
9
|
+
constructor(child: Operator<Data>, oldFieldName: OldFieldName, newFieldName: NewFieldName) {
|
|
10
|
+
super(
|
|
11
|
+
child,
|
|
12
|
+
(value) =>
|
|
13
|
+
({
|
|
14
|
+
...value,
|
|
15
|
+
[newFieldName]: value[oldFieldName],
|
|
16
|
+
}) as Data & { [key in NewFieldName]: Data[OldFieldName] },
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type FunctionComponent } from 'preact';
|
|
2
2
|
import { useContext, useRef, useState } from 'preact/hooks';
|
|
3
|
+
import { type JSXInternal } from 'preact/src/jsx';
|
|
3
4
|
|
|
4
5
|
import { fetchAutocompletionList } from './fetchAutocompletionList';
|
|
5
6
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
@@ -36,7 +37,7 @@ export const LocationFilterInner = ({ initialValue, fields }: LocationFilterInne
|
|
|
36
37
|
const [value, setValue] = useState(initialValue ?? '');
|
|
37
38
|
const [unknownLocation, setUnknownLocation] = useState(false);
|
|
38
39
|
|
|
39
|
-
const
|
|
40
|
+
const divRef = useRef<HTMLDivElement>(null);
|
|
40
41
|
|
|
41
42
|
const { data, error, isLoading } = useQuery(() => fetchAutocompletionList(fields, lapis), [fields, lapis]);
|
|
42
43
|
|
|
@@ -47,40 +48,29 @@ export const LocationFilterInner = ({ initialValue, fields }: LocationFilterInne
|
|
|
47
48
|
return <ErrorDisplay error={error} />;
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
const onInput = (event:
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const eventDetail = parseLocation(inputValue, fields);
|
|
56
|
-
if (hasMatchingEntry(data, eventDetail)) {
|
|
57
|
-
setUnknownLocation(false);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
51
|
+
const onInput = (event: JSXInternal.TargetedInputEvent<HTMLInputElement>) => {
|
|
52
|
+
const inputValue = event.currentTarget.value;
|
|
53
|
+
setValue(inputValue);
|
|
54
|
+
if (inputValue.trim() === value.trim()) {
|
|
55
|
+
return;
|
|
60
56
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const submit = (event: SubmitEvent) => {
|
|
64
|
-
event.preventDefault();
|
|
65
|
-
const eventDetail = parseLocation(value, fields);
|
|
66
|
-
|
|
57
|
+
const eventDetail = parseLocation(inputValue, fields);
|
|
67
58
|
if (hasMatchingEntry(data, eventDetail)) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
formRef.current?.dispatchEvent(
|
|
59
|
+
divRef.current?.dispatchEvent(
|
|
71
60
|
new CustomEvent('gs-location-changed', {
|
|
72
61
|
detail: eventDetail,
|
|
73
62
|
bubbles: true,
|
|
74
63
|
composed: true,
|
|
75
64
|
}),
|
|
76
65
|
);
|
|
66
|
+
setUnknownLocation(false);
|
|
77
67
|
} else {
|
|
78
68
|
setUnknownLocation(true);
|
|
79
69
|
}
|
|
80
70
|
};
|
|
81
71
|
|
|
82
72
|
return (
|
|
83
|
-
<
|
|
73
|
+
<div class='flex w-full' ref={divRef}>
|
|
84
74
|
<input
|
|
85
75
|
type='text'
|
|
86
76
|
class={`input input-bordered grow ${unknownLocation ? 'border-2 border-error' : ''}`}
|
|
@@ -97,10 +87,7 @@ export const LocationFilterInner = ({ initialValue, fields }: LocationFilterInne
|
|
|
97
87
|
return <option key={value} value={value} />;
|
|
98
88
|
})}
|
|
99
89
|
</datalist>
|
|
100
|
-
|
|
101
|
-
Submit
|
|
102
|
-
</button>
|
|
103
|
-
</form>
|
|
90
|
+
</div>
|
|
104
91
|
);
|
|
105
92
|
};
|
|
106
93
|
|
|
@@ -48,6 +48,7 @@ const Template = {
|
|
|
48
48
|
width={args.width}
|
|
49
49
|
height={args.height}
|
|
50
50
|
headline={args.headline}
|
|
51
|
+
lapisDateField={args.lapisDateField}
|
|
51
52
|
/>
|
|
52
53
|
</LapisUrlContext.Provider>
|
|
53
54
|
),
|
|
@@ -68,6 +69,7 @@ export const TwoVariants = {
|
|
|
68
69
|
width: '100%',
|
|
69
70
|
height: '700px',
|
|
70
71
|
headline: 'Prevalence over time',
|
|
72
|
+
lapisDateField: 'date',
|
|
71
73
|
},
|
|
72
74
|
parameters: {
|
|
73
75
|
fetchMock: {
|
|
@@ -139,6 +141,7 @@ export const OneVariant = {
|
|
|
139
141
|
width: '100%',
|
|
140
142
|
height: '700px',
|
|
141
143
|
headline: 'Prevalence over time',
|
|
144
|
+
lapisDateField: 'date',
|
|
142
145
|
},
|
|
143
146
|
parameters: {
|
|
144
147
|
fetchMock: {
|
|
@@ -39,6 +39,7 @@ export interface PrevalenceOverTimeInnerProps {
|
|
|
39
39
|
smoothingWindow: number;
|
|
40
40
|
views: View[];
|
|
41
41
|
confidenceIntervalMethods: ConfidenceIntervalMethod[];
|
|
42
|
+
lapisDateField: string;
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
export const PrevalenceOverTime: FunctionComponent<PrevalenceOverTimeProps> = ({
|
|
@@ -51,6 +52,7 @@ export const PrevalenceOverTime: FunctionComponent<PrevalenceOverTimeProps> = ({
|
|
|
51
52
|
width,
|
|
52
53
|
height,
|
|
53
54
|
headline = 'Prevalence over time',
|
|
55
|
+
lapisDateField,
|
|
54
56
|
}) => {
|
|
55
57
|
const size = { height, width };
|
|
56
58
|
|
|
@@ -65,6 +67,7 @@ export const PrevalenceOverTime: FunctionComponent<PrevalenceOverTimeProps> = ({
|
|
|
65
67
|
smoothingWindow={smoothingWindow}
|
|
66
68
|
views={views}
|
|
67
69
|
confidenceIntervalMethods={confidenceIntervalMethods}
|
|
70
|
+
lapisDateField={lapisDateField}
|
|
68
71
|
/>
|
|
69
72
|
</Headline>
|
|
70
73
|
</ResizeContainer>
|
|
@@ -79,11 +82,12 @@ export const PrevalenceOverTimeInner: FunctionComponent<PrevalenceOverTimeInnerP
|
|
|
79
82
|
smoothingWindow,
|
|
80
83
|
views,
|
|
81
84
|
confidenceIntervalMethods,
|
|
85
|
+
lapisDateField,
|
|
82
86
|
}) => {
|
|
83
87
|
const lapis = useContext(LapisUrlContext);
|
|
84
88
|
|
|
85
89
|
const { data, error, isLoading } = useQuery(
|
|
86
|
-
() => queryPrevalenceOverTime(numerator, denominator, granularity, smoothingWindow, lapis),
|
|
90
|
+
() => queryPrevalenceOverTime(numerator, denominator, granularity, smoothingWindow, lapis, lapisDateField),
|
|
87
91
|
[lapis, numerator, denominator, granularity, smoothingWindow],
|
|
88
92
|
);
|
|
89
93
|
|
|
@@ -35,6 +35,7 @@ export const Primary = {
|
|
|
35
35
|
width={args.width}
|
|
36
36
|
height={args.height}
|
|
37
37
|
headline={args.headline}
|
|
38
|
+
lapisDateField={args.lapisDateField}
|
|
38
39
|
/>
|
|
39
40
|
</LapisUrlContext.Provider>
|
|
40
41
|
),
|
|
@@ -46,6 +47,7 @@ export const Primary = {
|
|
|
46
47
|
width: '100%',
|
|
47
48
|
height: '700px',
|
|
48
49
|
headline: 'Relative growth advantage',
|
|
50
|
+
lapisDateField: 'date',
|
|
49
51
|
},
|
|
50
52
|
parameters: {
|
|
51
53
|
fetchMock: {
|
|
@@ -33,6 +33,7 @@ export interface RelativeGrowthAdvantagePropsInner {
|
|
|
33
33
|
denominator: LapisFilter;
|
|
34
34
|
generationTime: number;
|
|
35
35
|
views: View[];
|
|
36
|
+
lapisDateField: string;
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
export const RelativeGrowthAdvantage: FunctionComponent<RelativeGrowthAdvantageProps> = ({
|
|
@@ -43,6 +44,7 @@ export const RelativeGrowthAdvantage: FunctionComponent<RelativeGrowthAdvantageP
|
|
|
43
44
|
denominator,
|
|
44
45
|
generationTime,
|
|
45
46
|
headline = 'Relative growth advantage',
|
|
47
|
+
lapisDateField,
|
|
46
48
|
}) => {
|
|
47
49
|
const size = { height, width };
|
|
48
50
|
|
|
@@ -55,6 +57,7 @@ export const RelativeGrowthAdvantage: FunctionComponent<RelativeGrowthAdvantageP
|
|
|
55
57
|
numerator={numerator}
|
|
56
58
|
denominator={denominator}
|
|
57
59
|
generationTime={generationTime}
|
|
60
|
+
lapisDateField={lapisDateField}
|
|
58
61
|
/>
|
|
59
62
|
</Headline>
|
|
60
63
|
</ResizeContainer>
|
|
@@ -67,12 +70,13 @@ export const RelativeGrowthAdvantageInner: FunctionComponent<RelativeGrowthAdvan
|
|
|
67
70
|
denominator,
|
|
68
71
|
generationTime,
|
|
69
72
|
views,
|
|
73
|
+
lapisDateField,
|
|
70
74
|
}) => {
|
|
71
75
|
const lapis = useContext(LapisUrlContext);
|
|
72
76
|
const [yAxisScaleType, setYAxisScaleType] = useState<ScaleType>('linear');
|
|
73
77
|
|
|
74
78
|
const { data, error, isLoading } = useQuery(
|
|
75
|
-
() => queryRelativeGrowthAdvantage(numerator, denominator, generationTime, lapis),
|
|
79
|
+
() => queryRelativeGrowthAdvantage(numerator, denominator, generationTime, lapis, lapisDateField),
|
|
76
80
|
[lapis, numerator, denominator, generationTime, views],
|
|
77
81
|
);
|
|
78
82
|
|
|
@@ -3,6 +3,7 @@ import { FetchAggregatedOperator } from '../operator/FetchAggregatedOperator';
|
|
|
3
3
|
import { FillMissingOperator } from '../operator/FillMissingOperator';
|
|
4
4
|
import { GroupByAndSumOperator } from '../operator/GroupByAndSumOperator';
|
|
5
5
|
import { MapOperator } from '../operator/MapOperator';
|
|
6
|
+
import { RenameFieldOperator } from '../operator/RenameFieldOperator';
|
|
6
7
|
import { SlidingOperator } from '../operator/SlidingOperator';
|
|
7
8
|
import { SortOperator } from '../operator/SortOperator';
|
|
8
9
|
import { type LapisFilter, type NamedLapisFilter, type TemporalGranularity } from '../types';
|
|
@@ -27,14 +28,15 @@ export function queryPrevalenceOverTime(
|
|
|
27
28
|
granularity: TemporalGranularity,
|
|
28
29
|
smoothingWindow: number,
|
|
29
30
|
lapis: string,
|
|
31
|
+
lapisDateField: string,
|
|
30
32
|
signal?: AbortSignal,
|
|
31
33
|
): Promise<PrevalenceOverTimeData> {
|
|
32
34
|
const numeratorFilters = makeArray(numeratorFilter);
|
|
33
35
|
|
|
34
|
-
const denominatorData = fetchAndPrepare(denominatorFilter, granularity, smoothingWindow);
|
|
36
|
+
const denominatorData = fetchAndPrepare(denominatorFilter, granularity, smoothingWindow, lapisDateField);
|
|
35
37
|
const subQueries = numeratorFilters.map(async (namedLapisFilter) => {
|
|
36
38
|
const { displayName, lapisFilter } = namedLapisFilter;
|
|
37
|
-
const numeratorData = fetchAndPrepare(lapisFilter, granularity, smoothingWindow);
|
|
39
|
+
const numeratorData = fetchAndPrepare(lapisFilter, granularity, smoothingWindow, lapisDateField);
|
|
38
40
|
const divide = new DivisionOperator(
|
|
39
41
|
numeratorData,
|
|
40
42
|
denominatorData,
|
|
@@ -60,11 +62,15 @@ function makeArray<T>(arrayOrSingleItem: T | T[]) {
|
|
|
60
62
|
return [arrayOrSingleItem];
|
|
61
63
|
}
|
|
62
64
|
|
|
63
|
-
function fetchAndPrepare
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
function fetchAndPrepare<LapisDateField extends string>(
|
|
66
|
+
filter: LapisFilter,
|
|
67
|
+
granularity: TemporalGranularity,
|
|
68
|
+
smoothingWindow: number,
|
|
69
|
+
lapisDateField: LapisDateField,
|
|
70
|
+
) {
|
|
71
|
+
const fetchData = new FetchAggregatedOperator<{ [key in LapisDateField]: string | null }>(filter, [lapisDateField]);
|
|
72
|
+
const dataWithFixedDateKey = new RenameFieldOperator(fetchData, lapisDateField, 'date');
|
|
73
|
+
const mapData = new MapOperator(dataWithFixedDateKey, (d) => mapDateToGranularityRange(d, granularity));
|
|
68
74
|
const groupByData = new GroupByAndSumOperator(mapData, 'dateRange', 'count');
|
|
69
75
|
const fillData = new FillMissingOperator(
|
|
70
76
|
groupByData,
|
|
@@ -1,25 +1,29 @@
|
|
|
1
1
|
import { FetchAggregatedOperator } from '../operator/FetchAggregatedOperator';
|
|
2
2
|
import { MapOperator } from '../operator/MapOperator';
|
|
3
|
+
import { RenameFieldOperator } from '../operator/RenameFieldOperator';
|
|
3
4
|
import { type LapisFilter } from '../types';
|
|
4
5
|
import { getMinMaxTemporal, TemporalCache, type YearMonthDay } from '../utils/temporal';
|
|
5
6
|
|
|
6
7
|
export type RelativeGrowthAdvantageData = Awaited<ReturnType<typeof queryRelativeGrowthAdvantage>>;
|
|
7
8
|
|
|
8
|
-
export async function queryRelativeGrowthAdvantage(
|
|
9
|
+
export async function queryRelativeGrowthAdvantage<LapisDateField extends string>(
|
|
9
10
|
numerator: LapisFilter,
|
|
10
11
|
denominator: LapisFilter,
|
|
11
12
|
generationTime: number,
|
|
12
13
|
lapis: string,
|
|
14
|
+
lapisDateField: LapisDateField,
|
|
13
15
|
signal?: AbortSignal,
|
|
14
16
|
) {
|
|
15
17
|
const fetchNumerator = new FetchAggregatedOperator<{
|
|
16
|
-
|
|
17
|
-
}>(numerator, [
|
|
18
|
+
[key in LapisDateField]: string | null;
|
|
19
|
+
}>(numerator, [lapisDateField]);
|
|
18
20
|
const fetchDenominator = new FetchAggregatedOperator<{
|
|
19
|
-
|
|
20
|
-
}>(denominator, [
|
|
21
|
-
const
|
|
22
|
-
const
|
|
21
|
+
[key in LapisDateField]: string | null;
|
|
22
|
+
}>(denominator, [lapisDateField]);
|
|
23
|
+
const mapToFixedDateKeyNumerator = new RenameFieldOperator(fetchNumerator, lapisDateField, 'date');
|
|
24
|
+
const mapToFixedDateKeyDenominator = new RenameFieldOperator(fetchDenominator, lapisDateField, 'date');
|
|
25
|
+
const mapNumerator = new MapOperator(mapToFixedDateKeyNumerator, toYearMonthDay);
|
|
26
|
+
const mapDenominator = new MapOperator(mapToFixedDateKeyDenominator, toYearMonthDay);
|
|
23
27
|
const [numeratorData, denominatorData] = await Promise.all([
|
|
24
28
|
mapNumerator.evaluate(lapis, signal),
|
|
25
29
|
mapDenominator.evaluate(lapis, signal),
|
|
@@ -169,7 +169,6 @@ export const FiresEvent: StoryObj<LocationFilterProps> = {
|
|
|
169
169
|
play: async ({ canvasElement, step }) => {
|
|
170
170
|
const canvas = await withinShadowRoot(canvasElement, 'gs-location-filter');
|
|
171
171
|
|
|
172
|
-
const submitButton = () => canvas.getByRole('button', { name: 'Submit' });
|
|
173
172
|
const inputField = () => canvas.getByRole('combobox');
|
|
174
173
|
|
|
175
174
|
const listenerMock = fn();
|
|
@@ -185,14 +184,12 @@ export const FiresEvent: StoryObj<LocationFilterProps> = {
|
|
|
185
184
|
|
|
186
185
|
await step('Input invalid location', async () => {
|
|
187
186
|
await userEvent.type(inputField(), 'Not / A / Location');
|
|
188
|
-
await userEvent.click(submitButton());
|
|
189
187
|
await expect(listenerMock).not.toHaveBeenCalled();
|
|
190
188
|
await userEvent.type(inputField(), '{backspace>18/}');
|
|
191
189
|
});
|
|
192
190
|
|
|
193
191
|
await step('Select Asia', async () => {
|
|
194
192
|
await userEvent.type(inputField(), 'Asia');
|
|
195
|
-
await userEvent.click(submitButton());
|
|
196
193
|
await expect(listenerMock).toHaveBeenCalledWith(
|
|
197
194
|
expect.objectContaining({
|
|
198
195
|
detail: {
|
|
@@ -204,7 +201,6 @@ export const FiresEvent: StoryObj<LocationFilterProps> = {
|
|
|
204
201
|
|
|
205
202
|
await step('Select Asia / Bangladesh / Rajshahi / Chapainawabgonj', async () => {
|
|
206
203
|
await userEvent.type(inputField(), ' / Bangladesh / Rajshahi / Chapainawabgonj');
|
|
207
|
-
await userEvent.click(submitButton());
|
|
208
204
|
await expect(listenerMock).toHaveBeenCalledWith(
|
|
209
205
|
expect.objectContaining({
|
|
210
206
|
detail: {
|
|
@@ -18,7 +18,7 @@ import { PreactLitAdapter } from '../PreactLitAdapter';
|
|
|
18
18
|
* Values for the fields `i > K` are considered `undefined`.
|
|
19
19
|
*
|
|
20
20
|
* @fires {CustomEvent<Record<string, string>>} gs-location-changed
|
|
21
|
-
* Fired when the
|
|
21
|
+
* Fired when a value from the datalist is selected or when a valid value is typed into the field.
|
|
22
22
|
* The `details` of this event contain an object with all `fields` as keys
|
|
23
23
|
* and the corresponding values as values, if they are not `undefined`.
|
|
24
24
|
* Example:
|
|
@@ -23,8 +23,9 @@ const codeExample = String.raw`
|
|
|
23
23
|
views='["bar", "line", "bubble", "table"]'
|
|
24
24
|
confidenceIntervalMethods='["wilson"]'
|
|
25
25
|
headline="Prevalence over time"
|
|
26
|
-
width=
|
|
27
|
-
height=
|
|
26
|
+
width="100%"
|
|
27
|
+
height="700px"
|
|
28
|
+
lapisDateField="date"
|
|
28
29
|
></gs-prevalence-over-time>`;
|
|
29
30
|
|
|
30
31
|
const meta: Meta<Required<PrevalenceOverTimeProps>> = {
|
|
@@ -75,6 +76,7 @@ const Template: StoryObj<Required<PrevalenceOverTimeProps>> = {
|
|
|
75
76
|
.width=${args.width}
|
|
76
77
|
.height=${args.height}
|
|
77
78
|
.headline=${args.headline}
|
|
79
|
+
.lapisDateField=${args.lapisDateField}
|
|
78
80
|
></gs-prevalence-over-time>
|
|
79
81
|
</gs-app>
|
|
80
82
|
`,
|
|
@@ -95,6 +97,7 @@ export const TwoVariants: StoryObj<Required<PrevalenceOverTimeProps>> = {
|
|
|
95
97
|
width: '100%',
|
|
96
98
|
height: '700px',
|
|
97
99
|
headline: 'Prevalence over time',
|
|
100
|
+
lapisDateField: 'date',
|
|
98
101
|
},
|
|
99
102
|
parameters: {
|
|
100
103
|
fetchMock: {
|
|
@@ -166,6 +169,7 @@ export const OneVariant: StoryObj<Required<PrevalenceOverTimeProps>> = {
|
|
|
166
169
|
width: '100%',
|
|
167
170
|
height: '700px',
|
|
168
171
|
headline: 'Prevalence over time',
|
|
172
|
+
lapisDateField: 'date',
|
|
169
173
|
},
|
|
170
174
|
parameters: {
|
|
171
175
|
fetchMock: {
|
|
@@ -126,6 +126,16 @@ export class PrevalenceOverTimeComponent extends PreactLitAdapterWithGridJsStyle
|
|
|
126
126
|
@property({ type: String })
|
|
127
127
|
height: string = '700px';
|
|
128
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Required.
|
|
131
|
+
*
|
|
132
|
+
* The LAPIS field that the data should be aggregated by.
|
|
133
|
+
* The values will be used on the x-axis of the diagram.
|
|
134
|
+
* Must be a field of type `date` in LAPIS.
|
|
135
|
+
*/
|
|
136
|
+
@property({ type: String })
|
|
137
|
+
lapisDateField: string = 'date';
|
|
138
|
+
|
|
129
139
|
override render() {
|
|
130
140
|
return (
|
|
131
141
|
<PrevalenceOverTime
|
|
@@ -138,6 +148,7 @@ export class PrevalenceOverTimeComponent extends PreactLitAdapterWithGridJsStyle
|
|
|
138
148
|
width={this.width}
|
|
139
149
|
height={this.height}
|
|
140
150
|
headline={this.headline}
|
|
151
|
+
lapisDateField={this.lapisDateField}
|
|
141
152
|
/>
|
|
142
153
|
);
|
|
143
154
|
}
|
|
@@ -18,6 +18,7 @@ const codeExample = String.raw`
|
|
|
18
18
|
width='100%'
|
|
19
19
|
height='700px'
|
|
20
20
|
headline="Relative growth advantage"
|
|
21
|
+
lapisDateField="date"
|
|
21
22
|
></gs-relative-growth-advantage>`;
|
|
22
23
|
|
|
23
24
|
const meta: Meta<RelativeGrowthAdvantageProps> = {
|
|
@@ -58,6 +59,7 @@ const Template: StoryObj<Required<RelativeGrowthAdvantageProps>> = {
|
|
|
58
59
|
.width=${args.width}
|
|
59
60
|
.height=${args.height}
|
|
60
61
|
.headline=${args.headline}
|
|
62
|
+
.lapisDateField=${args.lapisDateField}
|
|
61
63
|
></gs-relative-growth-advantage>
|
|
62
64
|
</gs-app>
|
|
63
65
|
`,
|
|
@@ -73,6 +75,7 @@ export const Default: StoryObj<Required<RelativeGrowthAdvantageProps>> = {
|
|
|
73
75
|
width: '100%',
|
|
74
76
|
height: '700px',
|
|
75
77
|
headline: 'Relative growth advantage',
|
|
78
|
+
lapisDateField: 'date',
|
|
76
79
|
},
|
|
77
80
|
parameters: {
|
|
78
81
|
fetchMock: {
|
|
@@ -84,6 +84,16 @@ export class RelativeGrowthAdvantageComponent extends PreactLitAdapter {
|
|
|
84
84
|
@property({ type: String })
|
|
85
85
|
height: string = '700px';
|
|
86
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Required.
|
|
89
|
+
*
|
|
90
|
+
* The LAPIS field that the data should be aggregated by.
|
|
91
|
+
* The values will be used on the x-axis of the diagram.
|
|
92
|
+
* Must be a field of type `date` in LAPIS.
|
|
93
|
+
*/
|
|
94
|
+
@property({ type: String })
|
|
95
|
+
lapisDateField: string = 'date';
|
|
96
|
+
|
|
87
97
|
override render() {
|
|
88
98
|
return (
|
|
89
99
|
<RelativeGrowthAdvantage
|
|
@@ -94,6 +104,7 @@ export class RelativeGrowthAdvantageComponent extends PreactLitAdapter {
|
|
|
94
104
|
width={this.width}
|
|
95
105
|
height={this.height}
|
|
96
106
|
headline={this.headline}
|
|
107
|
+
lapisDateField={this.lapisDateField}
|
|
97
108
|
/>
|
|
98
109
|
);
|
|
99
110
|
}
|