@genspectrum/dashboard-components 0.15.0 → 0.16.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/custom-elements.json +200 -56
- package/dist/components.d.ts +35 -35
- package/dist/components.js +311 -192
- package/dist/components.js.map +1 -1
- package/dist/style.css +9 -0
- package/dist/util.d.ts +46 -46
- package/package.json +1 -1
- package/src/preact/ReferenceGenomeContext.ts +14 -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/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/mutations-over-time.stories.tsx +0 -2
- package/src/preact/mutationsOverTime/mutations-over-time.tsx +1 -1
- 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/ResizeContainer.mdx +4 -1
- 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 +9 -1
- package/src/web-components/visualization/gs-mutations-over-time.tsx +1 -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 +4965 -4876
- 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,6 +10,7 @@ import { type MutationsFilter, mutationsFilterSchema } from '../../types';
|
|
|
10
10
|
import { type DeletionClass, type InsertionClass, type SubstitutionClass } from '../../utils/mutations';
|
|
11
11
|
import { ReferenceGenomeContext } from '../ReferenceGenomeContext';
|
|
12
12
|
import { ErrorBoundary } from '../components/error-boundary';
|
|
13
|
+
import { UserFacingError } from '../components/error-display';
|
|
13
14
|
import { singleGraphColorRGBByName } from '../shared/charts/colors';
|
|
14
15
|
|
|
15
16
|
const mutationFilterInnerPropsSchema = z.object({
|
|
@@ -54,6 +55,13 @@ export const MutationFilterInner: FunctionComponent<MutationFilterInnerProps> =
|
|
|
54
55
|
|
|
55
56
|
const filterRef = useRef<HTMLDivElement>(null);
|
|
56
57
|
|
|
58
|
+
if (referenceGenome.nucleotideSequences.length === 0 && referenceGenome.genes.length === 0) {
|
|
59
|
+
throw new UserFacingError(
|
|
60
|
+
'No reference sequences available',
|
|
61
|
+
'This organism has neither nucleotide nor amino acid sequences configured in its reference genome. You cannot filter by mutations.',
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
57
65
|
const handleRemoveValue = (option: ParsedMutationFilter) => {
|
|
58
66
|
const newSelectedFilters = {
|
|
59
67
|
...selectedFilters,
|
|
@@ -41,7 +41,7 @@ const mutationsPropsSchema = z.object({
|
|
|
41
41
|
views: mutationsViewSchema.array(),
|
|
42
42
|
pageSize: z.union([z.boolean(), z.number()]),
|
|
43
43
|
width: z.string(),
|
|
44
|
-
height: z.string(),
|
|
44
|
+
height: z.string().optional(),
|
|
45
45
|
});
|
|
46
46
|
export type MutationsProps = z.infer<typeof mutationsPropsSchema>;
|
|
47
47
|
|
|
@@ -62,7 +62,6 @@ export const Default: StoryObj<MutationsOverTimeProps> = {
|
|
|
62
62
|
sequenceType: 'nucleotide',
|
|
63
63
|
views: ['grid'],
|
|
64
64
|
width: '100%',
|
|
65
|
-
height: '700px',
|
|
66
65
|
granularity: 'month',
|
|
67
66
|
lapisDateField: 'date',
|
|
68
67
|
},
|
|
@@ -76,7 +75,6 @@ export const ShowsMessageWhenTooManyMutations: StoryObj<MutationsOverTimeProps>
|
|
|
76
75
|
sequenceType: 'nucleotide',
|
|
77
76
|
views: ['grid'],
|
|
78
77
|
width: '100%',
|
|
79
|
-
height: '700px',
|
|
80
78
|
granularity: 'year',
|
|
81
79
|
lapisDateField: 'date',
|
|
82
80
|
},
|
|
@@ -45,7 +45,7 @@ const mutationOverTimeSchema = z.object({
|
|
|
45
45
|
granularity: temporalGranularitySchema,
|
|
46
46
|
lapisDateField: z.string().min(1),
|
|
47
47
|
width: z.string(),
|
|
48
|
-
height: z.string(),
|
|
48
|
+
height: z.string().optional(),
|
|
49
49
|
});
|
|
50
50
|
export type MutationsOverTimeProps = z.infer<typeof mutationOverTimeSchema>;
|
|
51
51
|
|
|
@@ -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,
|