@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.
Files changed (86) hide show
  1. package/custom-elements.json +255 -57
  2. package/dist/components.d.ts +49 -32
  3. package/dist/components.js +361 -212
  4. package/dist/components.js.map +1 -1
  5. package/dist/style.css +9 -0
  6. package/dist/util.d.ts +43 -43
  7. package/package.json +1 -1
  8. package/src/preact/ReferenceGenomeContext.ts +16 -1
  9. package/src/preact/aggregatedData/aggregate-bar-chart.tsx +26 -5
  10. package/src/preact/aggregatedData/aggregate.stories.tsx +0 -1
  11. package/src/preact/aggregatedData/aggregate.tsx +5 -1
  12. package/src/preact/components/ReferenceGenomesAwaiter.tsx +1 -6
  13. package/src/preact/components/info.tsx +1 -0
  14. package/src/preact/components/resize-container.tsx +1 -1
  15. package/src/preact/mutationComparison/mutation-comparison-venn.tsx +4 -2
  16. package/src/preact/mutationComparison/mutation-comparison.stories.tsx +0 -1
  17. package/src/preact/mutationComparison/mutation-comparison.tsx +5 -1
  18. package/src/preact/mutationFilter/mutation-filter.stories.tsx +17 -1
  19. package/src/preact/mutationFilter/mutation-filter.tsx +8 -0
  20. package/src/preact/mutations/mutations.stories.tsx +0 -1
  21. package/src/preact/mutations/mutations.tsx +1 -1
  22. package/src/preact/mutationsOverTime/getFilteredMutationsOverTime.spec.ts +70 -14
  23. package/src/preact/mutationsOverTime/getFilteredMutationsOverTimeData.ts +30 -7
  24. package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +56 -55
  25. package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +26 -39
  26. package/src/preact/mutationsOverTime/mutations-over-time.tsx +22 -7
  27. package/src/preact/numberSequencesOverTime/number-sequences-over-time-bar-chart.tsx +8 -3
  28. package/src/preact/numberSequencesOverTime/number-sequences-over-time-line-chart.tsx +8 -3
  29. package/src/preact/numberSequencesOverTime/number-sequences-over-time.stories.tsx +3 -1
  30. package/src/preact/numberSequencesOverTime/number-sequences-over-time.tsx +18 -3
  31. package/src/preact/prevalenceOverTime/prevalence-over-time-bar-chart.tsx +48 -35
  32. package/src/preact/prevalenceOverTime/prevalence-over-time-bubble-chart.tsx +83 -70
  33. package/src/preact/prevalenceOverTime/prevalence-over-time-line-chart.tsx +48 -37
  34. package/src/preact/prevalenceOverTime/prevalence-over-time.stories.tsx +0 -3
  35. package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +6 -1
  36. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage-chart.tsx +31 -23
  37. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.stories.tsx +0 -1
  38. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +5 -1
  39. package/src/preact/sequencesByLocation/__mockData__/worldAtlas.json +1 -0
  40. package/src/preact/{map → sequencesByLocation}/sequences-by-location-map.tsx +6 -3
  41. package/src/preact/{map → sequencesByLocation}/sequences-by-location-table.tsx +1 -1
  42. package/src/preact/{map → sequencesByLocation}/sequences-by-location.stories.tsx +58 -1
  43. package/src/preact/{map → sequencesByLocation}/sequences-by-location.tsx +10 -1
  44. package/src/preact/shared/aspectRatio/AspectRatio.tsx +13 -0
  45. package/src/preact/shared/charts/getMaintainAspectRatio.ts +3 -0
  46. package/src/preact/statistic/statistics.stories.tsx +0 -1
  47. package/src/preact/statistic/statistics.tsx +4 -4
  48. package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.stories.tsx +0 -1
  49. package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.tsx +1 -1
  50. package/src/query/computeMapLocationData.spec.ts +1 -1
  51. package/src/query/computeMapLocationData.ts +1 -1
  52. package/src/query/querySequencesByLocationData.ts +1 -1
  53. package/src/utilEntrypoint.ts +1 -1
  54. package/src/web-components/PreactLitAdapter.tsx +2 -5
  55. package/src/web-components/ResizeContainer.mdx +4 -1
  56. package/src/web-components/gs-app.ts +2 -4
  57. package/src/web-components/visualization/gs-aggregate.stories.ts +13 -6
  58. package/src/web-components/visualization/gs-aggregate.tsx +1 -1
  59. package/src/web-components/visualization/gs-mutation-comparison.stories.ts +8 -1
  60. package/src/web-components/visualization/gs-mutation-comparison.tsx +1 -1
  61. package/src/web-components/visualization/gs-mutations-over-time.stories.ts +24 -1
  62. package/src/web-components/visualization/gs-mutations-over-time.tsx +30 -1
  63. package/src/web-components/visualization/gs-mutations.stories.ts +8 -1
  64. package/src/web-components/visualization/gs-mutations.tsx +1 -1
  65. package/src/web-components/visualization/gs-number-sequences-over-time.stories.ts +11 -1
  66. package/src/web-components/visualization/gs-number-sequences-over-time.tsx +1 -1
  67. package/src/web-components/visualization/gs-prevalence-over-time.stories.ts +8 -2
  68. package/src/web-components/visualization/gs-prevalence-over-time.tsx +1 -1
  69. package/src/web-components/visualization/gs-relative-growth-advantage.stories.ts +8 -1
  70. package/src/web-components/visualization/gs-relative-growth-advantage.tsx +1 -1
  71. package/src/web-components/visualization/gs-sequences-by-location.stories.ts +13 -7
  72. package/src/web-components/visualization/gs-sequences-by-location.tsx +6 -3
  73. package/src/web-components/visualization/gs-statistics.stories.ts +0 -1
  74. package/src/web-components/visualization/gs-statistics.tsx +1 -1
  75. package/src/web-components/wastewaterVisualization/gs-wastewater-mutations-over-time.stories.ts +9 -1
  76. package/src/web-components/wastewaterVisualization/gs-wastewater-mutations-over-time.tsx +1 -1
  77. package/standalone-bundle/dashboard-components.js +5817 -5706
  78. package/standalone-bundle/dashboard-components.js.map +1 -1
  79. package/standalone-bundle/style.css +1 -1
  80. package/src/preact/map/__mockData__/worldAtlas.json +0 -497127
  81. /package/src/preact/{map → sequencesByLocation}/__mockData__/aggregatedGermany.json +0 -0
  82. /package/src/preact/{map → sequencesByLocation}/__mockData__/aggregatedWorld.json +0 -0
  83. /package/src/preact/{map → sequencesByLocation}/__mockData__/germanyMap.json +0 -0
  84. /package/src/preact/{map → sequencesByLocation}/__mockData__/howToGenerateWorldMap.md +0 -0
  85. /package/src/preact/{map → sequencesByLocation}/leafletStyleModifications.css +0 -0
  86. /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 = ({ data, yAxisScaleType }: NumberSequencesOverBarChartProps) => {
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: false,
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 = ({ data, yAxisScaleType }: NumberSequencesOverBarChartProps) => {
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: false,
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: <NumberSequencesOverTimeBarChart data={data} yAxisScaleType={yAxisScaleType} />,
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: <NumberSequencesOverTimeLineChart data={data} yAxisScaleType={yAxisScaleType} />,
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 nullFirstData = data
35
- .filter((prevalenceOverTimeData) => prevalenceOverTimeData.content.length > 0)
36
- .map((variantData) => {
37
- return {
38
- content: variantData.content.sort(sortNullToBeginningThenByDate),
39
- displayName: variantData.displayName,
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
- if (nullFirstData.length === 0) {
44
- return <NoDataDisplay />;
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
- plugins: {
66
- legend: {
67
- display: false,
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 nonNullDateRangeData = data
28
- .filter((prevalenceOverTimeData) => prevalenceOverTimeData.content.length > 0)
29
- .map((variantData) => {
30
- return {
31
- content: variantData.content.filter((dataPoint) => dataPoint.dateRange !== null),
32
- displayName: variantData.displayName,
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
- if (nonNullDateRangeData.length === 0) {
37
- return <NoDataDisplay />;
38
- }
42
+ if (nonNullDateRangeData.length === 0) {
43
+ return NO_DATA;
44
+ }
39
45
 
40
- const firstDate = nonNullDateRangeData[0].content[0].dateRange!;
41
- const total = nonNullDateRangeData.map((graphData) => graphData.content.map((dataPoint) => dataPoint.total)).flat();
42
- const [minTotal, maxTotal] = getMinMaxNumber(total)!;
43
- const scaleBubble = (value: number) => {
44
- return ((value - minTotal) / (maxTotal - minTotal)) * 4.5 + 0.5;
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
- const maxY =
48
- yAxisScaleType !== 'logit'
49
- ? getYAxisMax(maxInData(nonNullDateRangeData), yAxisMaxConfig?.[yAxisScaleType])
50
- : undefined;
55
+ const maxY =
56
+ yAxisScaleType !== 'logit'
57
+ ? getYAxisMax(maxInData(nonNullDateRangeData), yAxisMaxConfig?.[yAxisScaleType])
58
+ : undefined;
51
59
 
52
- const config: ChartConfiguration = {
53
- type: 'bubble',
54
- data: {
55
- datasets: nonNullDateRangeData.map((graphData, index) => ({
56
- label: graphData.displayName,
57
- data: graphData.content
58
- .filter((dataPoint) => dataPoint.dateRange !== null)
59
- .map((dataPoint) => ({
60
- x: minusTemporal(dataPoint.dateRange!, firstDate),
61
- y: dataPoint.prevalence,
62
- r: scaleBubble(dataPoint.total),
63
- })),
64
- borderWidth: 1,
65
- pointRadius: 0,
66
- backgroundColor: singleGraphColorRGBAById(index, 0.3),
67
- borderColor: singleGraphColorRGBAById(index),
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
- plugins: {
82
- legend: {
83
- display: false,
84
- },
85
- tooltip: {
86
- mode: 'index',
87
- intersect: false,
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
- label: (context) => {
95
- const dataset = nonNullDateRangeData[context.datasetIndex];
96
- const dataPoint = dataset.content[context.dataIndex];
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
- const percentage = (dataPoint.prevalence * 100).toFixed(2);
99
- const count = dataPoint.count.toFixed(0);
100
- const total = dataPoint.total.toFixed(0);
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
- return `${dataset.displayName}: ${percentage}%, ${count}/${total} samples`;
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 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
- });
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
- if (nonNullDateRangeData.length === 0) {
43
- return <NoDataDisplay />;
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
- plugins: {
67
- legend: {
68
- display: false,
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
  };