@genspectrum/dashboard-components 0.4.5 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/custom-elements.json +178 -102
- package/dist/dashboard-components.js +318 -147
- package/dist/dashboard-components.js.map +1 -1
- package/dist/genspectrum-components.d.ts +91 -55
- package/dist/style.css +34 -7
- package/package.json +5 -5
- package/src/preact/mutationComparison/mutation-comparison-venn.tsx +1 -1
- package/src/preact/mutationComparison/mutation-comparison.stories.tsx +18 -18
- package/src/preact/mutationComparison/mutation-comparison.tsx +6 -6
- package/src/preact/mutationComparison/queryMutationData.ts +4 -4
- package/src/preact/mutations/mutations.stories.tsx +3 -3
- package/src/preact/mutations/mutations.tsx +16 -6
- package/src/preact/mutations/queryMutations.ts +3 -3
- package/src/preact/prevalenceOverTime/__mockData__/{denominatorOneVariant.json → denominatorFilterOneDataset.json} +1 -1
- package/src/preact/prevalenceOverTime/__mockData__/{numeratorOneVariant.json → numeratorFilterOneDataset.json} +1 -1
- package/src/preact/prevalenceOverTime/prevalence-over-time-bar-chart.tsx +42 -5
- package/src/preact/prevalenceOverTime/prevalence-over-time-bubble-chart.tsx +26 -7
- package/src/preact/prevalenceOverTime/prevalence-over-time-line-chart.tsx +62 -28
- package/src/preact/prevalenceOverTime/prevalence-over-time.stories.tsx +26 -16
- package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +38 -11
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage-chart.tsx +39 -7
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.stories.tsx +10 -4
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +19 -10
- package/src/preact/shared/charts/confideceInterval.ts +7 -2
- package/src/preact/shared/charts/getYAxisMax.ts +24 -0
- package/src/preact/shared/charts/getYAxisScale.ts +1 -3
- package/src/query/queryAggregateData.ts +2 -2
- package/src/query/queryInsertions.ts +7 -2
- package/src/query/querySubstitutionsOrDeletions.ts +2 -2
- package/src/web-components/input/gs-date-range-selector.tsx +1 -1
- package/src/web-components/input/gs-location-filter.tsx +1 -1
- package/src/web-components/input/gs-mutation-filter.tsx +1 -1
- package/src/web-components/input/gs-text-input.tsx +1 -1
- package/src/web-components/visualization/gs-aggregate.tsx +2 -2
- package/src/web-components/visualization/gs-mutation-comparison.stories.ts +12 -12
- package/src/web-components/visualization/gs-mutation-comparison.tsx +18 -19
- package/src/web-components/visualization/gs-mutations.stories.ts +4 -4
- package/src/web-components/visualization/gs-mutations.tsx +10 -11
- package/src/web-components/visualization/gs-prevalence-over-time.stories.ts +46 -35
- package/src/web-components/visualization/gs-prevalence-over-time.tsx +54 -20
- package/src/web-components/visualization/gs-relative-growth-advantage.stories.ts +32 -18
- package/src/web-components/visualization/gs-relative-growth-advantage.tsx +51 -13
- /package/src/preact/mutationComparison/__mockData__/{nucleotideMutationsOtherVariant.json → nucleotideMutationsOtherDataset.json} +0 -0
- /package/src/preact/mutationComparison/__mockData__/{nucleotideMutationsSomeVariant.json → nucleotideMutationsSomeDataset.json} +0 -0
- /package/src/preact/prevalenceOverTime/__mockData__/{denominator.json → denominatorFilter.json} +0 -0
- /package/src/preact/prevalenceOverTime/__mockData__/{numeratorEG.json → numeratorFilterEG.json} +0 -0
- /package/src/preact/prevalenceOverTime/__mockData__/{numeratorJN1.json → numeratorFilterJN1.json} +0 -0
- /package/src/preact/relativeGrowthAdvantage/__mockData__/{denominator.json → denominatorFilter.json} +0 -0
- /package/src/preact/relativeGrowthAdvantage/__mockData__/{numerator.json → numeratorFilter.json} +0 -0
|
@@ -31,14 +31,14 @@ export interface MutationComparisonProps extends MutationComparisonInnerProps {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
export interface MutationComparisonInnerProps {
|
|
34
|
-
|
|
34
|
+
lapisFilters: NamedLapisFilter[];
|
|
35
35
|
sequenceType: SequenceType;
|
|
36
36
|
views: View[];
|
|
37
37
|
pageSize: boolean | number;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
export const MutationComparison: FunctionComponent<MutationComparisonProps> = ({
|
|
41
|
-
|
|
41
|
+
lapisFilters,
|
|
42
42
|
sequenceType,
|
|
43
43
|
views,
|
|
44
44
|
width,
|
|
@@ -53,7 +53,7 @@ export const MutationComparison: FunctionComponent<MutationComparisonProps> = ({
|
|
|
53
53
|
<ResizeContainer size={size}>
|
|
54
54
|
<Headline heading={headline}>
|
|
55
55
|
<MutationComparisonInner
|
|
56
|
-
|
|
56
|
+
lapisFilters={lapisFilters}
|
|
57
57
|
sequenceType={sequenceType}
|
|
58
58
|
views={views}
|
|
59
59
|
pageSize={pageSize}
|
|
@@ -65,7 +65,7 @@ export const MutationComparison: FunctionComponent<MutationComparisonProps> = ({
|
|
|
65
65
|
};
|
|
66
66
|
|
|
67
67
|
export const MutationComparisonInner: FunctionComponent<MutationComparisonInnerProps> = ({
|
|
68
|
-
|
|
68
|
+
lapisFilters,
|
|
69
69
|
sequenceType,
|
|
70
70
|
views,
|
|
71
71
|
pageSize,
|
|
@@ -73,8 +73,8 @@ export const MutationComparisonInner: FunctionComponent<MutationComparisonInnerP
|
|
|
73
73
|
const lapis = useContext(LapisUrlContext);
|
|
74
74
|
|
|
75
75
|
const { data, error, isLoading } = useQuery(async () => {
|
|
76
|
-
return queryMutationData(
|
|
77
|
-
}, [
|
|
76
|
+
return queryMutationData(lapisFilters, sequenceType, lapis);
|
|
77
|
+
}, [lapisFilters, sequenceType, lapis]);
|
|
78
78
|
|
|
79
79
|
if (isLoading) {
|
|
80
80
|
return <LoadingDisplay />;
|
|
@@ -9,15 +9,15 @@ export type MutationData = {
|
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
export async function queryMutationData(
|
|
12
|
-
|
|
12
|
+
lapisFilters: NamedLapisFilter[],
|
|
13
13
|
sequenceType: 'nucleotide' | 'amino acid',
|
|
14
14
|
lapis: string,
|
|
15
15
|
) {
|
|
16
16
|
const mutationData = await Promise.all(
|
|
17
|
-
|
|
17
|
+
lapisFilters.map(async (filter) => {
|
|
18
18
|
return {
|
|
19
|
-
displayName:
|
|
20
|
-
data: (await querySubstitutionsOrDeletions(
|
|
19
|
+
displayName: filter.displayName,
|
|
20
|
+
data: (await querySubstitutionsOrDeletions(filter.lapisFilter, sequenceType, lapis)).content,
|
|
21
21
|
};
|
|
22
22
|
}),
|
|
23
23
|
);
|
|
@@ -13,7 +13,7 @@ const meta: Meta<MutationsProps> = {
|
|
|
13
13
|
title: 'Visualization/Mutations',
|
|
14
14
|
component: Mutations,
|
|
15
15
|
argTypes: {
|
|
16
|
-
|
|
16
|
+
lapisFilter: { control: 'object' },
|
|
17
17
|
sequenceType: {
|
|
18
18
|
options: ['nucleotide', 'amino acid'],
|
|
19
19
|
control: { type: 'radio' },
|
|
@@ -36,7 +36,7 @@ const Template = {
|
|
|
36
36
|
<LapisUrlContext.Provider value={LAPIS_URL}>
|
|
37
37
|
<ReferenceGenomeContext.Provider value={referenceGenome}>
|
|
38
38
|
<Mutations
|
|
39
|
-
|
|
39
|
+
lapisFilter={args.lapisFilter}
|
|
40
40
|
sequenceType={args.sequenceType}
|
|
41
41
|
views={args.views}
|
|
42
42
|
width={args.width}
|
|
@@ -52,7 +52,7 @@ const Template = {
|
|
|
52
52
|
export const Default: StoryObj<MutationsProps> = {
|
|
53
53
|
...Template,
|
|
54
54
|
args: {
|
|
55
|
-
|
|
55
|
+
lapisFilter: { country: 'Switzerland', pangoLineage: 'B.1.1.7', dateTo: '2022-01-01' },
|
|
56
56
|
sequenceType: 'nucleotide',
|
|
57
57
|
views: ['grid', 'table', 'insertions'],
|
|
58
58
|
width: '100%',
|
|
@@ -32,7 +32,7 @@ import { useQuery } from '../useQuery';
|
|
|
32
32
|
export type View = 'table' | 'grid' | 'insertions';
|
|
33
33
|
|
|
34
34
|
export interface MutationsInnerProps {
|
|
35
|
-
|
|
35
|
+
lapisFilter: LapisFilter;
|
|
36
36
|
sequenceType: SequenceType;
|
|
37
37
|
views: View[];
|
|
38
38
|
pageSize: boolean | number;
|
|
@@ -45,7 +45,7 @@ export interface MutationsProps extends MutationsInnerProps {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
export const Mutations: FunctionComponent<MutationsProps> = ({
|
|
48
|
-
|
|
48
|
+
lapisFilter,
|
|
49
49
|
sequenceType,
|
|
50
50
|
views,
|
|
51
51
|
width,
|
|
@@ -59,18 +59,28 @@ export const Mutations: FunctionComponent<MutationsProps> = ({
|
|
|
59
59
|
<ErrorBoundary size={size} headline={headline}>
|
|
60
60
|
<ResizeContainer size={size}>
|
|
61
61
|
<Headline heading={headline}>
|
|
62
|
-
<MutationsInner
|
|
62
|
+
<MutationsInner
|
|
63
|
+
lapisFilter={lapisFilter}
|
|
64
|
+
sequenceType={sequenceType}
|
|
65
|
+
views={views}
|
|
66
|
+
pageSize={pageSize}
|
|
67
|
+
/>
|
|
63
68
|
</Headline>
|
|
64
69
|
</ResizeContainer>
|
|
65
70
|
</ErrorBoundary>
|
|
66
71
|
);
|
|
67
72
|
};
|
|
68
73
|
|
|
69
|
-
export const MutationsInner: FunctionComponent<MutationsInnerProps> = ({
|
|
74
|
+
export const MutationsInner: FunctionComponent<MutationsInnerProps> = ({
|
|
75
|
+
lapisFilter,
|
|
76
|
+
sequenceType,
|
|
77
|
+
views,
|
|
78
|
+
pageSize,
|
|
79
|
+
}) => {
|
|
70
80
|
const lapis = useContext(LapisUrlContext);
|
|
71
81
|
const { data, error, isLoading } = useQuery(async () => {
|
|
72
|
-
return queryMutationsData(
|
|
73
|
-
}, [
|
|
82
|
+
return queryMutationsData(lapisFilter, sequenceType, lapis);
|
|
83
|
+
}, [lapisFilter, sequenceType, lapis]);
|
|
74
84
|
|
|
75
85
|
if (isLoading) {
|
|
76
86
|
return <LoadingDisplay />;
|
|
@@ -10,12 +10,12 @@ import { type DisplayedSegment } from '../components/SegmentSelector';
|
|
|
10
10
|
import { type DisplayedMutationType } from '../components/mutation-type-selector';
|
|
11
11
|
|
|
12
12
|
export async function queryMutationsData(
|
|
13
|
-
|
|
13
|
+
lapisFilter: LapisFilter,
|
|
14
14
|
sequenceType: 'nucleotide' | 'amino acid',
|
|
15
15
|
lapis: string,
|
|
16
16
|
) {
|
|
17
|
-
const substitutionsOrDeletions = (await querySubstitutionsOrDeletions(
|
|
18
|
-
const insertions = (await queryInsertions(
|
|
17
|
+
const substitutionsOrDeletions = (await querySubstitutionsOrDeletions(lapisFilter, sequenceType, lapis)).content;
|
|
18
|
+
const insertions = (await queryInsertions(lapisFilter, sequenceType, lapis)).content;
|
|
19
19
|
|
|
20
20
|
return {
|
|
21
21
|
substitutionsOrDeletions,
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import { Chart, type ChartConfiguration, registerables, type TooltipItem } from 'chart.js';
|
|
2
2
|
import { BarWithErrorBar, BarWithErrorBarsController } from 'chartjs-chart-error-bars';
|
|
3
3
|
|
|
4
|
+
import { maxInData } from './prevalence-over-time';
|
|
4
5
|
import { type PrevalenceOverTimeData, type PrevalenceOverTimeVariantData } from '../../query/queryPrevalenceOverTime';
|
|
6
|
+
import type { Temporal } from '../../utils/temporal';
|
|
5
7
|
import GsChart from '../components/chart';
|
|
6
8
|
import { LogitScale } from '../shared/charts/LogitScale';
|
|
7
9
|
import { singleGraphColorRGBAById } from '../shared/charts/colors';
|
|
8
10
|
import { type ConfidenceIntervalMethod, wilson95PercentConfidenceInterval } from '../shared/charts/confideceInterval';
|
|
11
|
+
import { getYAxisMax, type YAxisMaxConfig } from '../shared/charts/getYAxisMax';
|
|
9
12
|
import { getYAxisScale, type ScaleType } from '../shared/charts/getYAxisScale';
|
|
10
13
|
|
|
11
14
|
interface PrevalenceOverTimeBarChartProps {
|
|
12
15
|
data: PrevalenceOverTimeData;
|
|
13
16
|
yAxisScaleType: ScaleType;
|
|
14
17
|
confidenceIntervalMethod: ConfidenceIntervalMethod;
|
|
18
|
+
yAxisMaxConfig: YAxisMaxConfig;
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
Chart.register(...registerables, LogitScale, BarWithErrorBarsController, BarWithErrorBar);
|
|
@@ -20,18 +24,32 @@ const PrevalenceOverTimeBarChart = ({
|
|
|
20
24
|
data,
|
|
21
25
|
yAxisScaleType,
|
|
22
26
|
confidenceIntervalMethod,
|
|
27
|
+
yAxisMaxConfig,
|
|
23
28
|
}: PrevalenceOverTimeBarChartProps) => {
|
|
29
|
+
const nullFirstData = data.map((variantData) => {
|
|
30
|
+
return {
|
|
31
|
+
content: variantData.content.sort(sortNullToBeginningThenByDate),
|
|
32
|
+
displayName: variantData.displayName,
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const datasets = nullFirstData.map((graphData, index) => getDataset(graphData, index, confidenceIntervalMethod));
|
|
37
|
+
|
|
38
|
+
const maxY =
|
|
39
|
+
yAxisScaleType !== 'logit'
|
|
40
|
+
? getYAxisMax(maxInData(nullFirstData), yAxisMaxConfig?.[yAxisScaleType])
|
|
41
|
+
: undefined;
|
|
42
|
+
|
|
24
43
|
const config: ChartConfiguration = {
|
|
25
44
|
type: BarWithErrorBarsController.id,
|
|
26
45
|
data: {
|
|
27
|
-
|
|
28
|
-
datasets: data.map((graphData, index) => datasets(graphData, index, confidenceIntervalMethod)),
|
|
46
|
+
datasets,
|
|
29
47
|
},
|
|
30
48
|
options: {
|
|
31
49
|
maintainAspectRatio: false,
|
|
32
50
|
animation: false,
|
|
33
51
|
scales: {
|
|
34
|
-
y: getYAxisScale(yAxisScaleType),
|
|
52
|
+
y: { ...getYAxisScale(yAxisScaleType), max: maxY },
|
|
35
53
|
},
|
|
36
54
|
plugins: {
|
|
37
55
|
legend: {
|
|
@@ -45,7 +63,23 @@ const PrevalenceOverTimeBarChart = ({
|
|
|
45
63
|
return <GsChart configuration={config} />;
|
|
46
64
|
};
|
|
47
65
|
|
|
48
|
-
|
|
66
|
+
function sortNullToBeginningThenByDate(
|
|
67
|
+
a: { count: number; prevalence: number; total: number; dateRange: Temporal | null },
|
|
68
|
+
b: {
|
|
69
|
+
count: number;
|
|
70
|
+
prevalence: number;
|
|
71
|
+
total: number;
|
|
72
|
+
dateRange: Temporal | null;
|
|
73
|
+
},
|
|
74
|
+
) {
|
|
75
|
+
return a.dateRange === null
|
|
76
|
+
? -1
|
|
77
|
+
: b.dateRange === null
|
|
78
|
+
? 1
|
|
79
|
+
: a.dateRange.toString().localeCompare(b.dateRange.toString());
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const getDataset = (
|
|
49
83
|
prevalenceOverTimeVariant: PrevalenceOverTimeVariantData,
|
|
50
84
|
index: number,
|
|
51
85
|
confidenceIntervalMethod: ConfidenceIntervalMethod,
|
|
@@ -66,12 +100,15 @@ const datasets = (
|
|
|
66
100
|
y: dataPoint.prevalence,
|
|
67
101
|
yMin: wilson95PercentConfidenceInterval(dataPoint.count, dataPoint.total).lowerLimit,
|
|
68
102
|
yMax: wilson95PercentConfidenceInterval(dataPoint.count, dataPoint.total).upperLimit,
|
|
103
|
+
x: dataPoint.dateRange?.toString() ?? 'Unknown',
|
|
69
104
|
})),
|
|
70
105
|
};
|
|
71
106
|
default:
|
|
72
107
|
return {
|
|
73
108
|
...generalConfig,
|
|
74
|
-
data: prevalenceOverTimeVariant.content.map((dataPoint) =>
|
|
109
|
+
data: prevalenceOverTimeVariant.content.map((dataPoint) => {
|
|
110
|
+
return { y: dataPoint.prevalence, x: dataPoint.dateRange };
|
|
111
|
+
}),
|
|
75
112
|
};
|
|
76
113
|
}
|
|
77
114
|
};
|
|
@@ -1,32 +1,51 @@
|
|
|
1
1
|
import { Chart, type ChartConfiguration, registerables } from 'chart.js';
|
|
2
2
|
|
|
3
|
+
import { maxInData } from './prevalence-over-time';
|
|
3
4
|
import { type PrevalenceOverTimeData } from '../../query/queryPrevalenceOverTime';
|
|
4
5
|
import { addUnit, minusTemporal } from '../../utils/temporal';
|
|
5
6
|
import { getMinMaxNumber } from '../../utils/utils';
|
|
6
7
|
import GsChart from '../components/chart';
|
|
7
8
|
import { LogitScale } from '../shared/charts/LogitScale';
|
|
8
9
|
import { singleGraphColorRGBAById } from '../shared/charts/colors';
|
|
10
|
+
import { getYAxisMax, type YAxisMaxConfig } from '../shared/charts/getYAxisMax';
|
|
9
11
|
import { getYAxisScale, type ScaleType } from '../shared/charts/getYAxisScale';
|
|
10
12
|
|
|
11
13
|
interface PrevalenceOverTimeBubbleChartProps {
|
|
12
14
|
data: PrevalenceOverTimeData;
|
|
13
15
|
yAxisScaleType: ScaleType;
|
|
16
|
+
yAxisMaxConfig: YAxisMaxConfig;
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
Chart.register(...registerables, LogitScale);
|
|
17
20
|
|
|
18
|
-
const PrevalenceOverTimeBubbleChart = ({
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
const PrevalenceOverTimeBubbleChart = ({
|
|
22
|
+
data,
|
|
23
|
+
yAxisScaleType,
|
|
24
|
+
yAxisMaxConfig,
|
|
25
|
+
}: PrevalenceOverTimeBubbleChartProps) => {
|
|
26
|
+
const nonNullDateRangeData = data.map((variantData) => {
|
|
27
|
+
return {
|
|
28
|
+
content: variantData.content.filter((dataPoint) => dataPoint.dateRange !== null),
|
|
29
|
+
displayName: variantData.displayName,
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const firstDate = nonNullDateRangeData[0].content[0].dateRange!;
|
|
34
|
+
const total = nonNullDateRangeData.map((graphData) => graphData.content.map((dataPoint) => dataPoint.total)).flat();
|
|
21
35
|
const [minTotal, maxTotal] = getMinMaxNumber(total)!;
|
|
22
36
|
const scaleBubble = (value: number) => {
|
|
23
37
|
return ((value - minTotal) / (maxTotal - minTotal)) * 4.5 + 0.5;
|
|
24
38
|
};
|
|
25
39
|
|
|
40
|
+
const maxY =
|
|
41
|
+
yAxisScaleType !== 'logit'
|
|
42
|
+
? getYAxisMax(maxInData(nonNullDateRangeData), yAxisMaxConfig?.[yAxisScaleType])
|
|
43
|
+
: undefined;
|
|
44
|
+
|
|
26
45
|
const config: ChartConfiguration = {
|
|
27
46
|
type: 'bubble',
|
|
28
47
|
data: {
|
|
29
|
-
datasets:
|
|
48
|
+
datasets: nonNullDateRangeData.map((graphData, index) => ({
|
|
30
49
|
label: graphData.displayName,
|
|
31
50
|
data: graphData.content
|
|
32
51
|
.filter((dataPoint) => dataPoint.dateRange !== null)
|
|
@@ -50,7 +69,7 @@ const PrevalenceOverTimeBubbleChart = ({ data, yAxisScaleType }: PrevalenceOverT
|
|
|
50
69
|
callback: (value) => addUnit(firstDate, value as number).toString(),
|
|
51
70
|
},
|
|
52
71
|
},
|
|
53
|
-
y: getYAxisScale(yAxisScaleType),
|
|
72
|
+
y: { ...getYAxisScale(yAxisScaleType), max: maxY },
|
|
54
73
|
},
|
|
55
74
|
plugins: {
|
|
56
75
|
legend: {
|
|
@@ -61,12 +80,12 @@ const PrevalenceOverTimeBubbleChart = ({ data, yAxisScaleType }: PrevalenceOverT
|
|
|
61
80
|
intersect: false,
|
|
62
81
|
callbacks: {
|
|
63
82
|
title: (context) => {
|
|
64
|
-
const dataset =
|
|
83
|
+
const dataset = nonNullDateRangeData[context[0].datasetIndex!];
|
|
65
84
|
const dataPoint = dataset.content[context[0].dataIndex!];
|
|
66
85
|
return dataPoint.dateRange?.toString();
|
|
67
86
|
},
|
|
68
87
|
label: (context) => {
|
|
69
|
-
const dataset =
|
|
88
|
+
const dataset = nonNullDateRangeData[context.datasetIndex!];
|
|
70
89
|
const dataPoint = dataset.content[context.dataIndex!];
|
|
71
90
|
|
|
72
91
|
const percentage = (dataPoint.prevalence * 100).toFixed(2);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Chart, type ChartConfiguration, registerables } from 'chart.js';
|
|
2
2
|
import { type TooltipItem } from 'chart.js/dist/types';
|
|
3
3
|
|
|
4
|
+
import { maxInData } from './prevalence-over-time';
|
|
4
5
|
import { type PrevalenceOverTimeData, type PrevalenceOverTimeVariantData } from '../../query/queryPrevalenceOverTime';
|
|
5
6
|
import GsChart from '../components/chart';
|
|
6
7
|
import { LogitScale } from '../shared/charts/LogitScale';
|
|
@@ -10,12 +11,14 @@ import {
|
|
|
10
11
|
type ConfidenceIntervalMethod,
|
|
11
12
|
wilson95PercentConfidenceInterval,
|
|
12
13
|
} from '../shared/charts/confideceInterval';
|
|
14
|
+
import { getYAxisMax, type YAxisMaxConfig } from '../shared/charts/getYAxisMax';
|
|
13
15
|
import { getYAxisScale, type ScaleType } from '../shared/charts/getYAxisScale';
|
|
14
16
|
|
|
15
17
|
interface PrevalenceOverTimeLineChartProps {
|
|
16
18
|
data: PrevalenceOverTimeData;
|
|
17
19
|
yAxisScaleType: ScaleType;
|
|
18
20
|
confidenceIntervalMethod: ConfidenceIntervalMethod;
|
|
21
|
+
yAxisMaxConfig: YAxisMaxConfig;
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
Chart.register(...registerables, LogitScale);
|
|
@@ -24,21 +27,34 @@ const PrevalenceOverTimeLineChart = ({
|
|
|
24
27
|
data,
|
|
25
28
|
yAxisScaleType,
|
|
26
29
|
confidenceIntervalMethod,
|
|
30
|
+
yAxisMaxConfig,
|
|
27
31
|
}: PrevalenceOverTimeLineChartProps) => {
|
|
28
|
-
const
|
|
29
|
-
|
|
32
|
+
const nonNullDateRangeData = data.map((variantData) => {
|
|
33
|
+
return {
|
|
34
|
+
content: variantData.content.filter((dataPoint) => dataPoint.dateRange !== null),
|
|
35
|
+
displayName: variantData.displayName,
|
|
36
|
+
};
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const datasets = nonNullDateRangeData
|
|
40
|
+
.map((graphData, index) => getDataset(graphData, index, confidenceIntervalMethod))
|
|
41
|
+
.flat();
|
|
42
|
+
|
|
43
|
+
const maxY =
|
|
44
|
+
yAxisScaleType !== 'logit'
|
|
45
|
+
? getYAxisMax(maxInData(nonNullDateRangeData), yAxisMaxConfig?.[yAxisScaleType])
|
|
46
|
+
: undefined;
|
|
30
47
|
|
|
31
48
|
const config: ChartConfiguration = {
|
|
32
49
|
type: 'line',
|
|
33
50
|
data: {
|
|
34
|
-
labels,
|
|
35
51
|
datasets,
|
|
36
52
|
},
|
|
37
53
|
options: {
|
|
38
54
|
animation: false,
|
|
39
55
|
maintainAspectRatio: false,
|
|
40
56
|
scales: {
|
|
41
|
-
y: getYAxisScale(yAxisScaleType),
|
|
57
|
+
y: { ...getYAxisScale(yAxisScaleType), max: maxY },
|
|
42
58
|
},
|
|
43
59
|
plugins: {
|
|
44
60
|
legend: {
|
|
@@ -71,9 +87,12 @@ const getDataset = (
|
|
|
71
87
|
|
|
72
88
|
const getDatasetCIUpper = (prevalenceOverTimeVariant: PrevalenceOverTimeVariantData, dataIndex: number) => ({
|
|
73
89
|
label: `${prevalenceOverTimeVariant.displayName} CI upper`,
|
|
74
|
-
data: prevalenceOverTimeVariant.content.map(
|
|
75
|
-
|
|
76
|
-
|
|
90
|
+
data: prevalenceOverTimeVariant.content.map((dataPoint): Datapoint => {
|
|
91
|
+
return {
|
|
92
|
+
y: wilson95PercentConfidenceInterval(dataPoint.count, dataPoint.total).upperLimit,
|
|
93
|
+
x: dataPoint.dateRange?.toString(),
|
|
94
|
+
};
|
|
95
|
+
}),
|
|
77
96
|
borderWidth: 0,
|
|
78
97
|
pointRadius: 0,
|
|
79
98
|
fill: '+1',
|
|
@@ -82,9 +101,12 @@ const getDatasetCIUpper = (prevalenceOverTimeVariant: PrevalenceOverTimeVariantD
|
|
|
82
101
|
|
|
83
102
|
const getDatasetCILower = (prevalenceOverTimeVariant: PrevalenceOverTimeVariantData, dataIndex: number) => ({
|
|
84
103
|
label: `${prevalenceOverTimeVariant.displayName} CI lower`,
|
|
85
|
-
data: prevalenceOverTimeVariant.content.map(
|
|
86
|
-
|
|
87
|
-
|
|
104
|
+
data: prevalenceOverTimeVariant.content.map((dataPoint): Datapoint => {
|
|
105
|
+
return {
|
|
106
|
+
y: wilson95PercentConfidenceInterval(dataPoint.count, dataPoint.total).lowerLimit,
|
|
107
|
+
x: dataPoint.dateRange?.toString(),
|
|
108
|
+
};
|
|
109
|
+
}),
|
|
88
110
|
borderWidth: 0,
|
|
89
111
|
pointRadius: 0,
|
|
90
112
|
fill: '-1',
|
|
@@ -93,13 +115,29 @@ const getDatasetCILower = (prevalenceOverTimeVariant: PrevalenceOverTimeVariantD
|
|
|
93
115
|
|
|
94
116
|
const getDatasetLine = (prevalenceOverTimeVariant: PrevalenceOverTimeVariantData, dataIndex: number) => ({
|
|
95
117
|
label: prevalenceOverTimeVariant.displayName,
|
|
96
|
-
data: prevalenceOverTimeVariant.content.map((dataPoint) =>
|
|
118
|
+
data: prevalenceOverTimeVariant.content.map((dataPoint): Datapoint => {
|
|
119
|
+
const ciLimits = wilson95PercentConfidenceInterval(dataPoint.count, dataPoint.total);
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
y: dataPoint.prevalence,
|
|
123
|
+
x: dataPoint.dateRange?.toString(),
|
|
124
|
+
yCiUpper: ciLimits.upperLimit,
|
|
125
|
+
yCiLower: ciLimits.lowerLimit,
|
|
126
|
+
};
|
|
127
|
+
}),
|
|
97
128
|
borderWidth: 1,
|
|
98
129
|
pointRadius: 0,
|
|
99
130
|
borderColor: singleGraphColorRGBAById(dataIndex),
|
|
100
131
|
backgroundColor: singleGraphColorRGBAById(dataIndex),
|
|
101
132
|
});
|
|
102
133
|
|
|
134
|
+
interface Datapoint {
|
|
135
|
+
y: number;
|
|
136
|
+
yCiLower?: number;
|
|
137
|
+
yCiUpper?: number;
|
|
138
|
+
x?: string;
|
|
139
|
+
}
|
|
140
|
+
|
|
103
141
|
const tooltip = (confidenceIntervalMethod?: ConfidenceIntervalMethod) => {
|
|
104
142
|
const generalConfig = {
|
|
105
143
|
mode: 'index' as const,
|
|
@@ -111,26 +149,18 @@ const tooltip = (confidenceIntervalMethod?: ConfidenceIntervalMethod) => {
|
|
|
111
149
|
return {
|
|
112
150
|
...generalConfig,
|
|
113
151
|
filter: ({ datasetIndex }: TooltipItem<'line'>) => {
|
|
114
|
-
return datasetIndex
|
|
152
|
+
return isNotCiIndex(datasetIndex);
|
|
115
153
|
},
|
|
116
154
|
callbacks: {
|
|
117
155
|
label: (context: TooltipItem<'line'>) => {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
typeof ciUpper !== 'number'
|
|
127
|
-
) {
|
|
128
|
-
return '';
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return confidenceIntervalDataLabel(value, ciLower, ciUpper, context.dataset.label);
|
|
132
|
-
}
|
|
133
|
-
return context.dataset.label;
|
|
156
|
+
const dataPoint = context.dataset.data[context.dataIndex] as unknown as Datapoint;
|
|
157
|
+
|
|
158
|
+
return confidenceIntervalDataLabel(
|
|
159
|
+
dataPoint.y,
|
|
160
|
+
dataPoint.yCiLower,
|
|
161
|
+
dataPoint.yCiUpper,
|
|
162
|
+
context.dataset.label,
|
|
163
|
+
);
|
|
134
164
|
},
|
|
135
165
|
},
|
|
136
166
|
};
|
|
@@ -139,4 +169,8 @@ const tooltip = (confidenceIntervalMethod?: ConfidenceIntervalMethod) => {
|
|
|
139
169
|
}
|
|
140
170
|
};
|
|
141
171
|
|
|
172
|
+
function isNotCiIndex(datasetIndex: number) {
|
|
173
|
+
return datasetIndex % 3 === 1;
|
|
174
|
+
}
|
|
175
|
+
|
|
142
176
|
export default PrevalenceOverTimeLineChart;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
1
|
+
import denominatorFilter from './__mockData__/denominatorFilter.json';
|
|
2
|
+
import denominatorOneDataset from './__mockData__/denominatorFilterOneDataset.json';
|
|
3
|
+
import numeratorFilterEG from './__mockData__/numeratorFilterEG.json';
|
|
4
|
+
import numeratorFilterJN1 from './__mockData__/numeratorFilterJN1.json';
|
|
5
|
+
import numeratorOneDataset from './__mockData__/numeratorFilterOneDataset.json';
|
|
6
6
|
import { PrevalenceOverTime, type PrevalenceOverTimeProps } from './prevalence-over-time';
|
|
7
7
|
import { AGGREGATED_ENDPOINT, LAPIS_URL } from '../../constants';
|
|
8
8
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
@@ -33,6 +33,7 @@ export default {
|
|
|
33
33
|
height: { control: 'text' },
|
|
34
34
|
headline: { control: 'text' },
|
|
35
35
|
pageSize: { control: 'object' },
|
|
36
|
+
yAxisMaxConfig: { control: 'object' },
|
|
36
37
|
},
|
|
37
38
|
};
|
|
38
39
|
|
|
@@ -40,8 +41,8 @@ const Template = {
|
|
|
40
41
|
render: (args: PrevalenceOverTimeProps) => (
|
|
41
42
|
<LapisUrlContext.Provider value={LAPIS_URL}>
|
|
42
43
|
<PrevalenceOverTime
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
numeratorFilter={args.numeratorFilter}
|
|
45
|
+
denominatorFilter={args.denominatorFilter}
|
|
45
46
|
granularity={args.granularity}
|
|
46
47
|
smoothingWindow={args.smoothingWindow}
|
|
47
48
|
views={args.views}
|
|
@@ -51,6 +52,7 @@ const Template = {
|
|
|
51
52
|
headline={args.headline}
|
|
52
53
|
lapisDateField={args.lapisDateField}
|
|
53
54
|
pageSize={args.pageSize}
|
|
55
|
+
yAxisMaxConfig={args.yAxisMaxConfig}
|
|
54
56
|
/>
|
|
55
57
|
</LapisUrlContext.Provider>
|
|
56
58
|
),
|
|
@@ -59,11 +61,11 @@ const Template = {
|
|
|
59
61
|
export const TwoVariants = {
|
|
60
62
|
...Template,
|
|
61
63
|
args: {
|
|
62
|
-
|
|
64
|
+
numeratorFilter: [
|
|
63
65
|
{ displayName: 'EG', lapisFilter: { country: 'USA', pangoLineage: 'EG*', dateFrom: '2023-01-01' } },
|
|
64
66
|
{ displayName: 'JN.1', lapisFilter: { country: 'USA', pangoLineage: 'JN.1*', dateFrom: '2023-01-01' } },
|
|
65
67
|
],
|
|
66
|
-
|
|
68
|
+
denominatorFilter: { country: 'USA', dateFrom: '2023-01-01' },
|
|
67
69
|
granularity: 'month',
|
|
68
70
|
smoothingWindow: 0,
|
|
69
71
|
views: ['bar', 'line', 'bubble', 'table'],
|
|
@@ -73,6 +75,10 @@ export const TwoVariants = {
|
|
|
73
75
|
headline: 'Prevalence over time',
|
|
74
76
|
lapisDateField: 'date',
|
|
75
77
|
pageSize: 10,
|
|
78
|
+
yAxisMaxConfig: {
|
|
79
|
+
linear: 1,
|
|
80
|
+
logarithmic: 1,
|
|
81
|
+
},
|
|
76
82
|
},
|
|
77
83
|
parameters: {
|
|
78
84
|
fetchMock: {
|
|
@@ -90,7 +96,7 @@ export const TwoVariants = {
|
|
|
90
96
|
},
|
|
91
97
|
response: {
|
|
92
98
|
status: 200,
|
|
93
|
-
body:
|
|
99
|
+
body: numeratorFilterEG,
|
|
94
100
|
},
|
|
95
101
|
},
|
|
96
102
|
{
|
|
@@ -106,7 +112,7 @@ export const TwoVariants = {
|
|
|
106
112
|
},
|
|
107
113
|
response: {
|
|
108
114
|
status: 200,
|
|
109
|
-
body:
|
|
115
|
+
body: numeratorFilterJN1,
|
|
110
116
|
},
|
|
111
117
|
},
|
|
112
118
|
{
|
|
@@ -121,7 +127,7 @@ export const TwoVariants = {
|
|
|
121
127
|
},
|
|
122
128
|
response: {
|
|
123
129
|
status: 200,
|
|
124
|
-
body:
|
|
130
|
+
body: denominatorFilter,
|
|
125
131
|
},
|
|
126
132
|
},
|
|
127
133
|
],
|
|
@@ -132,11 +138,11 @@ export const TwoVariants = {
|
|
|
132
138
|
export const OneVariant = {
|
|
133
139
|
...Template,
|
|
134
140
|
args: {
|
|
135
|
-
|
|
141
|
+
numeratorFilter: {
|
|
136
142
|
displayName: 'EG',
|
|
137
143
|
lapisFilter: { country: 'USA', pangoLineage: 'BA.2.86*', dateFrom: '2023-10-01' },
|
|
138
144
|
},
|
|
139
|
-
|
|
145
|
+
denominatorFilter: { country: 'USA', dateFrom: '2023-10-01' },
|
|
140
146
|
granularity: 'day',
|
|
141
147
|
smoothingWindow: 7,
|
|
142
148
|
views: ['bar', 'line', 'bubble', 'table'],
|
|
@@ -146,6 +152,10 @@ export const OneVariant = {
|
|
|
146
152
|
headline: 'Prevalence over time',
|
|
147
153
|
lapisDateField: 'date',
|
|
148
154
|
pageSize: 10,
|
|
155
|
+
yAxisMaxConfig: {
|
|
156
|
+
linear: 1,
|
|
157
|
+
logarithmic: 1,
|
|
158
|
+
},
|
|
149
159
|
},
|
|
150
160
|
parameters: {
|
|
151
161
|
fetchMock: {
|
|
@@ -163,7 +173,7 @@ export const OneVariant = {
|
|
|
163
173
|
},
|
|
164
174
|
response: {
|
|
165
175
|
status: 200,
|
|
166
|
-
body:
|
|
176
|
+
body: numeratorOneDataset,
|
|
167
177
|
},
|
|
168
178
|
},
|
|
169
179
|
{
|
|
@@ -178,7 +188,7 @@ export const OneVariant = {
|
|
|
178
188
|
},
|
|
179
189
|
response: {
|
|
180
190
|
status: 200,
|
|
181
|
-
body:
|
|
191
|
+
body: denominatorOneDataset,
|
|
182
192
|
},
|
|
183
193
|
},
|
|
184
194
|
],
|