@genspectrum/dashboard-components 0.15.0 → 0.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/custom-elements.json +255 -57
- package/dist/components.d.ts +49 -32
- package/dist/components.js +361 -212
- package/dist/components.js.map +1 -1
- package/dist/style.css +9 -0
- package/dist/util.d.ts +43 -43
- package/package.json +1 -1
- package/src/preact/ReferenceGenomeContext.ts +16 -1
- package/src/preact/aggregatedData/aggregate-bar-chart.tsx +26 -5
- package/src/preact/aggregatedData/aggregate.stories.tsx +0 -1
- package/src/preact/aggregatedData/aggregate.tsx +5 -1
- package/src/preact/components/ReferenceGenomesAwaiter.tsx +1 -6
- package/src/preact/components/info.tsx +1 -0
- package/src/preact/components/resize-container.tsx +1 -1
- package/src/preact/mutationComparison/mutation-comparison-venn.tsx +4 -2
- package/src/preact/mutationComparison/mutation-comparison.stories.tsx +0 -1
- package/src/preact/mutationComparison/mutation-comparison.tsx +5 -1
- package/src/preact/mutationFilter/mutation-filter.stories.tsx +17 -1
- package/src/preact/mutationFilter/mutation-filter.tsx +8 -0
- package/src/preact/mutations/mutations.stories.tsx +0 -1
- package/src/preact/mutations/mutations.tsx +1 -1
- package/src/preact/mutationsOverTime/getFilteredMutationsOverTime.spec.ts +70 -14
- package/src/preact/mutationsOverTime/getFilteredMutationsOverTimeData.ts +30 -7
- package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +56 -55
- package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +26 -39
- package/src/preact/mutationsOverTime/mutations-over-time.tsx +22 -7
- package/src/preact/numberSequencesOverTime/number-sequences-over-time-bar-chart.tsx +8 -3
- package/src/preact/numberSequencesOverTime/number-sequences-over-time-line-chart.tsx +8 -3
- package/src/preact/numberSequencesOverTime/number-sequences-over-time.stories.tsx +3 -1
- package/src/preact/numberSequencesOverTime/number-sequences-over-time.tsx +18 -3
- package/src/preact/prevalenceOverTime/prevalence-over-time-bar-chart.tsx +48 -35
- package/src/preact/prevalenceOverTime/prevalence-over-time-bubble-chart.tsx +83 -70
- package/src/preact/prevalenceOverTime/prevalence-over-time-line-chart.tsx +48 -37
- package/src/preact/prevalenceOverTime/prevalence-over-time.stories.tsx +0 -3
- package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +6 -1
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage-chart.tsx +31 -23
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.stories.tsx +0 -1
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +5 -1
- package/src/preact/sequencesByLocation/__mockData__/worldAtlas.json +1 -0
- package/src/preact/{map → sequencesByLocation}/sequences-by-location-map.tsx +6 -3
- package/src/preact/{map → sequencesByLocation}/sequences-by-location-table.tsx +1 -1
- package/src/preact/{map → sequencesByLocation}/sequences-by-location.stories.tsx +58 -1
- package/src/preact/{map → sequencesByLocation}/sequences-by-location.tsx +10 -1
- package/src/preact/shared/aspectRatio/AspectRatio.tsx +13 -0
- package/src/preact/shared/charts/getMaintainAspectRatio.ts +3 -0
- package/src/preact/statistic/statistics.stories.tsx +0 -1
- package/src/preact/statistic/statistics.tsx +4 -4
- package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.stories.tsx +0 -1
- package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.tsx +1 -1
- package/src/query/computeMapLocationData.spec.ts +1 -1
- package/src/query/computeMapLocationData.ts +1 -1
- package/src/query/querySequencesByLocationData.ts +1 -1
- package/src/utilEntrypoint.ts +1 -1
- package/src/web-components/PreactLitAdapter.tsx +2 -5
- package/src/web-components/ResizeContainer.mdx +4 -1
- package/src/web-components/gs-app.ts +2 -4
- package/src/web-components/visualization/gs-aggregate.stories.ts +13 -6
- package/src/web-components/visualization/gs-aggregate.tsx +1 -1
- package/src/web-components/visualization/gs-mutation-comparison.stories.ts +8 -1
- package/src/web-components/visualization/gs-mutation-comparison.tsx +1 -1
- package/src/web-components/visualization/gs-mutations-over-time.stories.ts +24 -1
- package/src/web-components/visualization/gs-mutations-over-time.tsx +30 -1
- package/src/web-components/visualization/gs-mutations.stories.ts +8 -1
- package/src/web-components/visualization/gs-mutations.tsx +1 -1
- package/src/web-components/visualization/gs-number-sequences-over-time.stories.ts +11 -1
- package/src/web-components/visualization/gs-number-sequences-over-time.tsx +1 -1
- package/src/web-components/visualization/gs-prevalence-over-time.stories.ts +8 -2
- package/src/web-components/visualization/gs-prevalence-over-time.tsx +1 -1
- package/src/web-components/visualization/gs-relative-growth-advantage.stories.ts +8 -1
- package/src/web-components/visualization/gs-relative-growth-advantage.tsx +1 -1
- package/src/web-components/visualization/gs-sequences-by-location.stories.ts +13 -7
- package/src/web-components/visualization/gs-sequences-by-location.tsx +6 -3
- package/src/web-components/visualization/gs-statistics.stories.ts +0 -1
- package/src/web-components/visualization/gs-statistics.tsx +1 -1
- package/src/web-components/wastewaterVisualization/gs-wastewater-mutations-over-time.stories.ts +9 -1
- package/src/web-components/wastewaterVisualization/gs-wastewater-mutations-over-time.tsx +1 -1
- package/standalone-bundle/dashboard-components.js +5817 -5706
- package/standalone-bundle/dashboard-components.js.map +1 -1
- package/standalone-bundle/style.css +1 -1
- package/src/preact/map/__mockData__/worldAtlas.json +0 -497127
- /package/src/preact/{map → sequencesByLocation}/__mockData__/aggregatedGermany.json +0 -0
- /package/src/preact/{map → sequencesByLocation}/__mockData__/aggregatedWorld.json +0 -0
- /package/src/preact/{map → sequencesByLocation}/__mockData__/germanyMap.json +0 -0
- /package/src/preact/{map → sequencesByLocation}/__mockData__/howToGenerateWorldMap.md +0 -0
- /package/src/preact/{map → sequencesByLocation}/leafletStyleModifications.css +0 -0
- /package/src/preact/{map → sequencesByLocation}/loadMapSource.tsx +0 -0
|
@@ -10,11 +10,16 @@ import { getYAxisScale, type ScaleType } from '../shared/charts/getYAxisScale';
|
|
|
10
10
|
interface NumberSequencesOverBarChartProps {
|
|
11
11
|
data: NumberOfSequencesDatasets;
|
|
12
12
|
yAxisScaleType: ScaleType;
|
|
13
|
+
maintainAspectRatio: boolean;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
Chart.register(...registerables);
|
|
16
17
|
|
|
17
|
-
export const NumberSequencesOverTimeBarChart = ({
|
|
18
|
+
export const NumberSequencesOverTimeBarChart = ({
|
|
19
|
+
data,
|
|
20
|
+
yAxisScaleType,
|
|
21
|
+
maintainAspectRatio,
|
|
22
|
+
}: NumberSequencesOverBarChartProps) => {
|
|
18
23
|
const config: ChartConfiguration = useMemo(
|
|
19
24
|
() => ({
|
|
20
25
|
type: 'bar',
|
|
@@ -22,7 +27,7 @@ export const NumberSequencesOverTimeBarChart = ({ data, yAxisScaleType }: Number
|
|
|
22
27
|
datasets: getDatasets(data),
|
|
23
28
|
},
|
|
24
29
|
options: {
|
|
25
|
-
maintainAspectRatio
|
|
30
|
+
maintainAspectRatio,
|
|
26
31
|
animation: false,
|
|
27
32
|
scales: {
|
|
28
33
|
y: {
|
|
@@ -40,7 +45,7 @@ export const NumberSequencesOverTimeBarChart = ({ data, yAxisScaleType }: Number
|
|
|
40
45
|
},
|
|
41
46
|
},
|
|
42
47
|
}),
|
|
43
|
-
[data, yAxisScaleType],
|
|
48
|
+
[data, maintainAspectRatio, yAxisScaleType],
|
|
44
49
|
);
|
|
45
50
|
|
|
46
51
|
return <GsChart configuration={config} />;
|
|
@@ -10,11 +10,16 @@ import { getYAxisScale, type ScaleType } from '../shared/charts/getYAxisScale';
|
|
|
10
10
|
interface NumberSequencesOverBarChartProps {
|
|
11
11
|
data: NumberOfSequencesDatasets;
|
|
12
12
|
yAxisScaleType: ScaleType;
|
|
13
|
+
maintainAspectRatio: boolean;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
Chart.register(...registerables);
|
|
16
17
|
|
|
17
|
-
export const NumberSequencesOverTimeLineChart = ({
|
|
18
|
+
export const NumberSequencesOverTimeLineChart = ({
|
|
19
|
+
data,
|
|
20
|
+
yAxisScaleType,
|
|
21
|
+
maintainAspectRatio,
|
|
22
|
+
}: NumberSequencesOverBarChartProps) => {
|
|
18
23
|
const config: ChartConfiguration = useMemo(
|
|
19
24
|
() => ({
|
|
20
25
|
type: 'line',
|
|
@@ -22,7 +27,7 @@ export const NumberSequencesOverTimeLineChart = ({ data, yAxisScaleType }: Numbe
|
|
|
22
27
|
datasets: getDatasets(data),
|
|
23
28
|
},
|
|
24
29
|
options: {
|
|
25
|
-
maintainAspectRatio
|
|
30
|
+
maintainAspectRatio,
|
|
26
31
|
animation: false,
|
|
27
32
|
scales: {
|
|
28
33
|
y: {
|
|
@@ -40,7 +45,7 @@ export const NumberSequencesOverTimeLineChart = ({ data, yAxisScaleType }: Numbe
|
|
|
40
45
|
},
|
|
41
46
|
},
|
|
42
47
|
}),
|
|
43
|
-
[data, yAxisScaleType],
|
|
48
|
+
[data, maintainAspectRatio, yAxisScaleType],
|
|
44
49
|
);
|
|
45
50
|
|
|
46
51
|
return <GsChart configuration={config} />;
|
|
@@ -24,6 +24,9 @@ export default {
|
|
|
24
24
|
control: { type: 'check' },
|
|
25
25
|
},
|
|
26
26
|
pageSize: { control: 'object' },
|
|
27
|
+
height: {
|
|
28
|
+
control: 'text',
|
|
29
|
+
},
|
|
27
30
|
},
|
|
28
31
|
};
|
|
29
32
|
|
|
@@ -40,7 +43,6 @@ const Template: StoryObj<NumberSequencesOverTimeProps> = {
|
|
|
40
43
|
],
|
|
41
44
|
lapisDateField: 'date',
|
|
42
45
|
width: '100%',
|
|
43
|
-
height: '700px',
|
|
44
46
|
smoothingWindow: 0,
|
|
45
47
|
granularity: 'month',
|
|
46
48
|
pageSize: 10,
|
|
@@ -21,6 +21,7 @@ import { NoDataDisplay } from '../components/no-data-display';
|
|
|
21
21
|
import { ResizeContainer } from '../components/resize-container';
|
|
22
22
|
import { ScalingSelector } from '../components/scaling-selector';
|
|
23
23
|
import Tabs from '../components/tabs';
|
|
24
|
+
import { getMaintainAspectRatio } from '../shared/charts/getMaintainAspectRatio';
|
|
24
25
|
import type { ScaleType } from '../shared/charts/getYAxisScale';
|
|
25
26
|
import { useQuery } from '../useQuery';
|
|
26
27
|
|
|
@@ -33,7 +34,7 @@ export type NumberSequencesOverTimeView = z.infer<typeof numberSequencesOverTime
|
|
|
33
34
|
|
|
34
35
|
const numberSequencesOverTimePropsSchema = z.object({
|
|
35
36
|
width: z.string(),
|
|
36
|
-
height: z.string(),
|
|
37
|
+
height: z.string().optional(),
|
|
37
38
|
lapisFilters: z.array(namedLapisFilterSchema).min(1),
|
|
38
39
|
lapisDateField: z.string().min(1),
|
|
39
40
|
views: z.array(numberSequencesOverTimeViewSchema),
|
|
@@ -89,17 +90,31 @@ interface NumberSequencesOverTimeTabsProps {
|
|
|
89
90
|
const NumberSequencesOverTimeTabs = ({ data, originalComponentProps }: NumberSequencesOverTimeTabsProps) => {
|
|
90
91
|
const [yAxisScaleType, setYAxisScaleType] = useState<ScaleType>('linear');
|
|
91
92
|
|
|
93
|
+
const maintainAspectRatio = getMaintainAspectRatio(originalComponentProps.height);
|
|
94
|
+
|
|
92
95
|
const getTab = (view: NumberSequencesOverTimeView) => {
|
|
93
96
|
switch (view) {
|
|
94
97
|
case 'bar':
|
|
95
98
|
return {
|
|
96
99
|
title: 'Bar',
|
|
97
|
-
content:
|
|
100
|
+
content: (
|
|
101
|
+
<NumberSequencesOverTimeBarChart
|
|
102
|
+
data={data}
|
|
103
|
+
yAxisScaleType={yAxisScaleType}
|
|
104
|
+
maintainAspectRatio={maintainAspectRatio}
|
|
105
|
+
/>
|
|
106
|
+
),
|
|
98
107
|
};
|
|
99
108
|
case 'line':
|
|
100
109
|
return {
|
|
101
110
|
title: 'Line',
|
|
102
|
-
content:
|
|
111
|
+
content: (
|
|
112
|
+
<NumberSequencesOverTimeLineChart
|
|
113
|
+
data={data}
|
|
114
|
+
yAxisScaleType={yAxisScaleType}
|
|
115
|
+
maintainAspectRatio={maintainAspectRatio}
|
|
116
|
+
/>
|
|
117
|
+
),
|
|
103
118
|
};
|
|
104
119
|
case 'table':
|
|
105
120
|
return {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Chart, type ChartConfiguration, type ChartDataset, registerables, type TooltipItem } from 'chart.js';
|
|
2
2
|
import { BarWithErrorBar, BarWithErrorBarsController } from 'chartjs-chart-error-bars';
|
|
3
|
+
import { useMemo } from 'preact/hooks';
|
|
3
4
|
|
|
4
5
|
import { maxInData } from './prevalence-over-time';
|
|
5
6
|
import {
|
|
@@ -21,55 +22,67 @@ interface PrevalenceOverTimeBarChartProps {
|
|
|
21
22
|
yAxisScaleType: ScaleType;
|
|
22
23
|
confidenceIntervalMethod: ConfidenceIntervalMethod;
|
|
23
24
|
yAxisMaxConfig: YAxisMaxConfig;
|
|
25
|
+
maintainAspectRatio: boolean;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
Chart.register(...registerables, LogitScale, BarWithErrorBarsController, BarWithErrorBar);
|
|
27
29
|
|
|
30
|
+
const NO_DATA = 'noData';
|
|
31
|
+
|
|
28
32
|
const PrevalenceOverTimeBarChart = ({
|
|
29
33
|
data,
|
|
30
34
|
yAxisScaleType,
|
|
31
35
|
confidenceIntervalMethod,
|
|
32
36
|
yAxisMaxConfig,
|
|
37
|
+
maintainAspectRatio,
|
|
33
38
|
}: PrevalenceOverTimeBarChartProps) => {
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
const config = useMemo<ChartConfiguration | typeof NO_DATA>(() => {
|
|
40
|
+
const nullFirstData = data
|
|
41
|
+
.filter((prevalenceOverTimeData) => prevalenceOverTimeData.content.length > 0)
|
|
42
|
+
.map((variantData) => {
|
|
43
|
+
return {
|
|
44
|
+
content: variantData.content.sort(sortNullToBeginningThenByDate),
|
|
45
|
+
displayName: variantData.displayName,
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (nullFirstData.length === 0) {
|
|
50
|
+
return NO_DATA;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const datasets = nullFirstData.map((graphData, index) =>
|
|
54
|
+
getDataset(graphData, index, confidenceIntervalMethod),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const maxY =
|
|
58
|
+
yAxisScaleType !== 'logit'
|
|
59
|
+
? getYAxisMax(maxInData(nullFirstData), yAxisMaxConfig?.[yAxisScaleType])
|
|
60
|
+
: undefined;
|
|
42
61
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const datasets = nullFirstData.map((graphData, index) => getDataset(graphData, index, confidenceIntervalMethod));
|
|
48
|
-
|
|
49
|
-
const maxY =
|
|
50
|
-
yAxisScaleType !== 'logit'
|
|
51
|
-
? getYAxisMax(maxInData(nullFirstData), yAxisMaxConfig?.[yAxisScaleType])
|
|
52
|
-
: undefined;
|
|
53
|
-
|
|
54
|
-
const config: ChartConfiguration = {
|
|
55
|
-
type: BarWithErrorBarsController.id,
|
|
56
|
-
data: {
|
|
57
|
-
datasets,
|
|
58
|
-
},
|
|
59
|
-
options: {
|
|
60
|
-
maintainAspectRatio: false,
|
|
61
|
-
animation: false,
|
|
62
|
-
scales: {
|
|
63
|
-
y: { ...getYAxisScale(yAxisScaleType), max: maxY },
|
|
62
|
+
return {
|
|
63
|
+
type: BarWithErrorBarsController.id,
|
|
64
|
+
data: {
|
|
65
|
+
datasets,
|
|
64
66
|
},
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
options: {
|
|
68
|
+
maintainAspectRatio,
|
|
69
|
+
animation: false,
|
|
70
|
+
scales: {
|
|
71
|
+
y: { ...getYAxisScale(yAxisScaleType), max: maxY },
|
|
72
|
+
},
|
|
73
|
+
plugins: {
|
|
74
|
+
legend: {
|
|
75
|
+
display: false,
|
|
76
|
+
},
|
|
77
|
+
tooltip: tooltip(confidenceIntervalMethod),
|
|
68
78
|
},
|
|
69
|
-
tooltip: tooltip(confidenceIntervalMethod),
|
|
70
79
|
},
|
|
71
|
-
}
|
|
72
|
-
};
|
|
80
|
+
};
|
|
81
|
+
}, [data, yAxisScaleType, confidenceIntervalMethod, yAxisMaxConfig, maintainAspectRatio]);
|
|
82
|
+
|
|
83
|
+
if (config === NO_DATA) {
|
|
84
|
+
return <NoDataDisplay />;
|
|
85
|
+
}
|
|
73
86
|
|
|
74
87
|
return <GsChart configuration={config} />;
|
|
75
88
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Chart, type ChartConfiguration, registerables } from 'chart.js';
|
|
2
|
+
import { useMemo } from 'preact/hooks';
|
|
2
3
|
|
|
3
4
|
import { maxInData } from './prevalence-over-time';
|
|
4
5
|
import { type PrevalenceOverTimeData } from '../../query/queryPrevalenceOverTime';
|
|
@@ -15,97 +16,109 @@ interface PrevalenceOverTimeBubbleChartProps {
|
|
|
15
16
|
data: PrevalenceOverTimeData;
|
|
16
17
|
yAxisScaleType: ScaleType;
|
|
17
18
|
yAxisMaxConfig: YAxisMaxConfig;
|
|
19
|
+
maintainAspectRatio: boolean;
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
Chart.register(...registerables, LogitScale);
|
|
21
23
|
|
|
24
|
+
const NO_DATA = 'noData';
|
|
25
|
+
|
|
22
26
|
const PrevalenceOverTimeBubbleChart = ({
|
|
23
27
|
data,
|
|
24
28
|
yAxisScaleType,
|
|
25
29
|
yAxisMaxConfig,
|
|
30
|
+
maintainAspectRatio,
|
|
26
31
|
}: PrevalenceOverTimeBubbleChartProps) => {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
const config = useMemo<ChartConfiguration | typeof NO_DATA>(() => {
|
|
33
|
+
const nonNullDateRangeData = data
|
|
34
|
+
.filter((prevalenceOverTimeData) => prevalenceOverTimeData.content.length > 0)
|
|
35
|
+
.map((variantData) => {
|
|
36
|
+
return {
|
|
37
|
+
content: variantData.content.filter((dataPoint) => dataPoint.dateRange !== null),
|
|
38
|
+
displayName: variantData.displayName,
|
|
39
|
+
};
|
|
40
|
+
});
|
|
35
41
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
42
|
+
if (nonNullDateRangeData.length === 0) {
|
|
43
|
+
return NO_DATA;
|
|
44
|
+
}
|
|
39
45
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
const firstDate = nonNullDateRangeData[0].content[0].dateRange!;
|
|
47
|
+
const total = nonNullDateRangeData
|
|
48
|
+
.map((graphData) => graphData.content.map((dataPoint) => dataPoint.total))
|
|
49
|
+
.flat();
|
|
50
|
+
const [minTotal, maxTotal] = getMinMaxNumber(total)!;
|
|
51
|
+
const scaleBubble = (value: number) => {
|
|
52
|
+
return ((value - minTotal) / (maxTotal - minTotal)) * 4.5 + 0.5;
|
|
53
|
+
};
|
|
46
54
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
55
|
+
const maxY =
|
|
56
|
+
yAxisScaleType !== 'logit'
|
|
57
|
+
? getYAxisMax(maxInData(nonNullDateRangeData), yAxisMaxConfig?.[yAxisScaleType])
|
|
58
|
+
: undefined;
|
|
51
59
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
},
|
|
70
|
-
options: {
|
|
71
|
-
animation: false,
|
|
72
|
-
maintainAspectRatio: false,
|
|
73
|
-
scales: {
|
|
74
|
-
x: {
|
|
75
|
-
ticks: {
|
|
76
|
-
callback: (value) => addUnit(firstDate, value as number).toString(),
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
y: { ...getYAxisScale(yAxisScaleType), max: maxY },
|
|
60
|
+
return {
|
|
61
|
+
type: 'bubble',
|
|
62
|
+
data: {
|
|
63
|
+
datasets: nonNullDateRangeData.map((graphData, index) => ({
|
|
64
|
+
label: graphData.displayName,
|
|
65
|
+
data: graphData.content
|
|
66
|
+
.filter((dataPoint) => dataPoint.dateRange !== null)
|
|
67
|
+
.map((dataPoint) => ({
|
|
68
|
+
x: minusTemporal(dataPoint.dateRange!, firstDate),
|
|
69
|
+
y: dataPoint.prevalence,
|
|
70
|
+
r: scaleBubble(dataPoint.total),
|
|
71
|
+
})),
|
|
72
|
+
borderWidth: 1,
|
|
73
|
+
pointRadius: 0,
|
|
74
|
+
backgroundColor: singleGraphColorRGBAById(index, 0.3),
|
|
75
|
+
borderColor: singleGraphColorRGBAById(index),
|
|
76
|
+
})),
|
|
80
77
|
},
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
callbacks: {
|
|
89
|
-
title: (context) => {
|
|
90
|
-
const dataset = nonNullDateRangeData[context[0].datasetIndex];
|
|
91
|
-
const dataPoint = dataset.content[context[0].dataIndex];
|
|
92
|
-
return dataPoint.dateRange?.toString();
|
|
78
|
+
options: {
|
|
79
|
+
animation: false,
|
|
80
|
+
maintainAspectRatio,
|
|
81
|
+
scales: {
|
|
82
|
+
x: {
|
|
83
|
+
ticks: {
|
|
84
|
+
callback: (value) => addUnit(firstDate, value as number).toString(),
|
|
93
85
|
},
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
86
|
+
},
|
|
87
|
+
y: { ...getYAxisScale(yAxisScaleType), max: maxY },
|
|
88
|
+
},
|
|
89
|
+
plugins: {
|
|
90
|
+
legend: {
|
|
91
|
+
display: false,
|
|
92
|
+
},
|
|
93
|
+
tooltip: {
|
|
94
|
+
mode: 'index',
|
|
95
|
+
intersect: false,
|
|
96
|
+
callbacks: {
|
|
97
|
+
title: (context) => {
|
|
98
|
+
const dataset = nonNullDateRangeData[context[0].datasetIndex];
|
|
99
|
+
const dataPoint = dataset.content[context[0].dataIndex];
|
|
100
|
+
return dataPoint.dateRange?.toString();
|
|
101
|
+
},
|
|
102
|
+
label: (context) => {
|
|
103
|
+
const dataset = nonNullDateRangeData[context.datasetIndex];
|
|
104
|
+
const dataPoint = dataset.content[context.dataIndex];
|
|
97
105
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
106
|
+
const percentage = (dataPoint.prevalence * 100).toFixed(2);
|
|
107
|
+
const count = dataPoint.count.toFixed(0);
|
|
108
|
+
const total = dataPoint.total.toFixed(0);
|
|
101
109
|
|
|
102
|
-
|
|
110
|
+
return `${dataset.displayName}: ${percentage}%, ${count}/${total} samples`;
|
|
111
|
+
},
|
|
103
112
|
},
|
|
104
113
|
},
|
|
105
114
|
},
|
|
106
115
|
},
|
|
107
|
-
}
|
|
108
|
-
};
|
|
116
|
+
} satisfies ChartConfiguration;
|
|
117
|
+
}, [data, maintainAspectRatio, yAxisMaxConfig, yAxisScaleType]);
|
|
118
|
+
|
|
119
|
+
if (config === NO_DATA) {
|
|
120
|
+
return <NoDataDisplay />;
|
|
121
|
+
}
|
|
109
122
|
|
|
110
123
|
return <GsChart configuration={config} />;
|
|
111
124
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Chart, type ChartConfiguration, registerables } from 'chart.js';
|
|
2
2
|
import { type TooltipItem } from 'chart.js/dist/types';
|
|
3
|
+
import { useMemo } from 'preact/hooks';
|
|
3
4
|
|
|
4
5
|
import { maxInData } from './prevalence-over-time';
|
|
5
6
|
import { type PrevalenceOverTimeData, type PrevalenceOverTimeVariantData } from '../../query/queryPrevalenceOverTime';
|
|
@@ -20,57 +21,67 @@ interface PrevalenceOverTimeLineChartProps {
|
|
|
20
21
|
yAxisScaleType: ScaleType;
|
|
21
22
|
confidenceIntervalMethod: ConfidenceIntervalMethod;
|
|
22
23
|
yAxisMaxConfig: YAxisMaxConfig;
|
|
24
|
+
maintainAspectRatio: boolean;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
Chart.register(...registerables, LogitScale);
|
|
26
28
|
|
|
29
|
+
const NO_DATA = 'noData';
|
|
30
|
+
|
|
27
31
|
const PrevalenceOverTimeLineChart = ({
|
|
28
32
|
data,
|
|
29
33
|
yAxisScaleType,
|
|
30
34
|
confidenceIntervalMethod,
|
|
31
35
|
yAxisMaxConfig,
|
|
36
|
+
maintainAspectRatio,
|
|
32
37
|
}: PrevalenceOverTimeLineChartProps) => {
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
const config = useMemo<ChartConfiguration | typeof NO_DATA>(() => {
|
|
39
|
+
const nonNullDateRangeData = data
|
|
40
|
+
.filter((prevalenceOverTimeData) => prevalenceOverTimeData.content.length > 0)
|
|
41
|
+
.map((variantData) => {
|
|
42
|
+
return {
|
|
43
|
+
content: variantData.content.filter((dataPoint) => dataPoint.dateRange !== null),
|
|
44
|
+
displayName: variantData.displayName,
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (nonNullDateRangeData.length === 0) {
|
|
49
|
+
return NO_DATA;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const datasets = nonNullDateRangeData
|
|
53
|
+
.map((graphData, index) => getDataset(graphData, index, confidenceIntervalMethod))
|
|
54
|
+
.flat();
|
|
55
|
+
|
|
56
|
+
const maxY =
|
|
57
|
+
yAxisScaleType !== 'logit'
|
|
58
|
+
? getYAxisMax(maxInData(nonNullDateRangeData), yAxisMaxConfig?.[yAxisScaleType])
|
|
59
|
+
: undefined;
|
|
41
60
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const datasets = nonNullDateRangeData
|
|
47
|
-
.map((graphData, index) => getDataset(graphData, index, confidenceIntervalMethod))
|
|
48
|
-
.flat();
|
|
49
|
-
|
|
50
|
-
const maxY =
|
|
51
|
-
yAxisScaleType !== 'logit'
|
|
52
|
-
? getYAxisMax(maxInData(nonNullDateRangeData), yAxisMaxConfig?.[yAxisScaleType])
|
|
53
|
-
: undefined;
|
|
54
|
-
|
|
55
|
-
const config: ChartConfiguration = {
|
|
56
|
-
type: 'line',
|
|
57
|
-
data: {
|
|
58
|
-
datasets,
|
|
59
|
-
},
|
|
60
|
-
options: {
|
|
61
|
-
animation: false,
|
|
62
|
-
maintainAspectRatio: false,
|
|
63
|
-
scales: {
|
|
64
|
-
y: { ...getYAxisScale(yAxisScaleType), max: maxY },
|
|
61
|
+
return {
|
|
62
|
+
type: 'line',
|
|
63
|
+
data: {
|
|
64
|
+
datasets,
|
|
65
65
|
},
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
options: {
|
|
67
|
+
animation: false,
|
|
68
|
+
maintainAspectRatio,
|
|
69
|
+
scales: {
|
|
70
|
+
y: { ...getYAxisScale(yAxisScaleType), max: maxY },
|
|
71
|
+
},
|
|
72
|
+
plugins: {
|
|
73
|
+
legend: {
|
|
74
|
+
display: false,
|
|
75
|
+
},
|
|
76
|
+
tooltip: tooltip(confidenceIntervalMethod),
|
|
69
77
|
},
|
|
70
|
-
tooltip: tooltip(confidenceIntervalMethod),
|
|
71
78
|
},
|
|
72
|
-
}
|
|
73
|
-
};
|
|
79
|
+
};
|
|
80
|
+
}, [data, yAxisScaleType, confidenceIntervalMethod, yAxisMaxConfig, maintainAspectRatio]);
|
|
81
|
+
|
|
82
|
+
if (config === NO_DATA) {
|
|
83
|
+
return <NoDataDisplay />;
|
|
84
|
+
}
|
|
74
85
|
|
|
75
86
|
return <GsChart configuration={config} />;
|
|
76
87
|
};
|
|
@@ -61,7 +61,6 @@ export const TwoVariants: StoryObj<PrevalenceOverTimeProps> = {
|
|
|
61
61
|
views: ['bar', 'line', 'bubble', 'table'],
|
|
62
62
|
confidenceIntervalMethods: ['none', 'wilson'],
|
|
63
63
|
width: '100%',
|
|
64
|
-
height: '700px',
|
|
65
64
|
lapisDateField: 'date',
|
|
66
65
|
pageSize: 10,
|
|
67
66
|
yAxisMaxLinear: 1,
|
|
@@ -137,7 +136,6 @@ export const OneVariant: StoryObj<PrevalenceOverTimeProps> = {
|
|
|
137
136
|
views: ['bar', 'line', 'bubble', 'table'],
|
|
138
137
|
confidenceIntervalMethods: ['none', 'wilson'],
|
|
139
138
|
width: '100%',
|
|
140
|
-
height: '700px',
|
|
141
139
|
lapisDateField: 'date',
|
|
142
140
|
pageSize: 10,
|
|
143
141
|
yAxisMaxLinear: 1,
|
|
@@ -197,7 +195,6 @@ export const ShowsNoDataBanner: StoryObj<PrevalenceOverTimeProps> = {
|
|
|
197
195
|
views: ['bar', 'line', 'bubble', 'table'],
|
|
198
196
|
confidenceIntervalMethods: ['none', 'wilson'],
|
|
199
197
|
width: '100%',
|
|
200
|
-
height: '700px',
|
|
201
198
|
lapisDateField: 'date',
|
|
202
199
|
pageSize: 10,
|
|
203
200
|
yAxisMaxLinear: 1,
|
|
@@ -21,6 +21,7 @@ import { ResizeContainer } from '../components/resize-container';
|
|
|
21
21
|
import { ScalingSelector } from '../components/scaling-selector';
|
|
22
22
|
import Tabs from '../components/tabs';
|
|
23
23
|
import { type ConfidenceIntervalMethod, confidenceIntervalMethodSchema } from '../shared/charts/confideceInterval';
|
|
24
|
+
import { getMaintainAspectRatio } from '../shared/charts/getMaintainAspectRatio';
|
|
24
25
|
import { axisMaxSchema } from '../shared/charts/getYAxisMax';
|
|
25
26
|
import { type ScaleType } from '../shared/charts/getYAxisScale';
|
|
26
27
|
import { useQuery } from '../useQuery';
|
|
@@ -35,7 +36,7 @@ export type PrevalenceOverTimeView = z.infer<typeof prevalenceOverTimeViewSchema
|
|
|
35
36
|
|
|
36
37
|
const prevalenceOverTimePropsSchema = z.object({
|
|
37
38
|
width: z.string(),
|
|
38
|
-
height: z.string(),
|
|
39
|
+
height: z.string().optional(),
|
|
39
40
|
numeratorFilters: z.array(namedLapisFilterSchema).min(1),
|
|
40
41
|
denominatorFilter: lapisFilterSchema,
|
|
41
42
|
granularity: temporalGranularitySchema,
|
|
@@ -117,6 +118,7 @@ const PrevalenceOverTimeTabs: FunctionComponent<PrevalenceOverTimeTabsProps> = (
|
|
|
117
118
|
}, [confidenceIntervalMethods]);
|
|
118
119
|
|
|
119
120
|
const yAxisMaxConfig = { linear: yAxisMaxLinear, logarithmic: yAxisMaxLogarithmic };
|
|
121
|
+
const maintainAspectRatio = getMaintainAspectRatio(componentProps.height);
|
|
120
122
|
|
|
121
123
|
const getTab = (view: PrevalenceOverTimeView) => {
|
|
122
124
|
switch (view) {
|
|
@@ -129,6 +131,7 @@ const PrevalenceOverTimeTabs: FunctionComponent<PrevalenceOverTimeTabsProps> = (
|
|
|
129
131
|
yAxisScaleType={yAxisScaleType}
|
|
130
132
|
confidenceIntervalMethod={confidenceIntervalMethod}
|
|
131
133
|
yAxisMaxConfig={yAxisMaxConfig}
|
|
134
|
+
maintainAspectRatio={maintainAspectRatio}
|
|
132
135
|
/>
|
|
133
136
|
),
|
|
134
137
|
};
|
|
@@ -141,6 +144,7 @@ const PrevalenceOverTimeTabs: FunctionComponent<PrevalenceOverTimeTabsProps> = (
|
|
|
141
144
|
yAxisScaleType={yAxisScaleType}
|
|
142
145
|
confidenceIntervalMethod={confidenceIntervalMethod}
|
|
143
146
|
yAxisMaxConfig={yAxisMaxConfig}
|
|
147
|
+
maintainAspectRatio={maintainAspectRatio}
|
|
144
148
|
/>
|
|
145
149
|
),
|
|
146
150
|
};
|
|
@@ -152,6 +156,7 @@ const PrevalenceOverTimeTabs: FunctionComponent<PrevalenceOverTimeTabsProps> = (
|
|
|
152
156
|
data={data}
|
|
153
157
|
yAxisScaleType={yAxisScaleType}
|
|
154
158
|
yAxisMaxConfig={yAxisMaxConfig}
|
|
159
|
+
maintainAspectRatio={maintainAspectRatio}
|
|
155
160
|
/>
|
|
156
161
|
),
|
|
157
162
|
};
|