@genspectrum/dashboard-components 1.11.0 → 1.12.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/dist/util.d.ts CHANGED
@@ -971,11 +971,7 @@ declare global {
971
971
 
972
972
  declare global {
973
973
  interface HTMLElementTagNameMap {
974
- 'gs-date-range-filter': DateRangeFilterComponent;
975
- }
976
- interface HTMLElementEventMap {
977
- [gsEventNames.dateRangeFilterChanged]: CustomEvent<Record<string, string>>;
978
- [gsEventNames.dateRangeOptionChanged]: DateRangeOptionChangedEvent;
974
+ 'gs-genome-data-viewer': GenomeDataViewerComponent;
979
975
  }
980
976
  }
981
977
 
@@ -983,7 +979,7 @@ declare global {
983
979
  declare global {
984
980
  namespace JSX {
985
981
  interface IntrinsicElements {
986
- 'gs-date-range-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
982
+ 'gs-genome-data-viewer': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
987
983
  }
988
984
  }
989
985
  }
@@ -991,10 +987,7 @@ declare global {
991
987
 
992
988
  declare global {
993
989
  interface HTMLElementTagNameMap {
994
- 'gs-location-filter': LocationFilterComponent;
995
- }
996
- interface HTMLElementEventMap {
997
- [gsEventNames.locationChanged]: LocationChangedEvent;
990
+ 'gs-mutation-comparison': MutationComparisonComponent;
998
991
  }
999
992
  }
1000
993
 
@@ -1002,7 +995,7 @@ declare global {
1002
995
  declare global {
1003
996
  namespace JSX {
1004
997
  interface IntrinsicElements {
1005
- 'gs-location-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
998
+ 'gs-mutation-comparison': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1006
999
  }
1007
1000
  }
1008
1001
  }
@@ -1010,10 +1003,7 @@ declare global {
1010
1003
 
1011
1004
  declare global {
1012
1005
  interface HTMLElementTagNameMap {
1013
- 'gs-text-filter': TextFilterComponent;
1014
- }
1015
- interface HTMLElementEventMap {
1016
- [gsEventNames.textFilterChanged]: TextFilterChangedEvent;
1006
+ 'gs-mutations': MutationsComponent;
1017
1007
  }
1018
1008
  }
1019
1009
 
@@ -1021,7 +1011,7 @@ declare global {
1021
1011
  declare global {
1022
1012
  namespace JSX {
1023
1013
  interface IntrinsicElements {
1024
- 'gs-text-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1014
+ 'gs-mutations': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1025
1015
  }
1026
1016
  }
1027
1017
  }
@@ -1029,11 +1019,7 @@ declare global {
1029
1019
 
1030
1020
  declare global {
1031
1021
  interface HTMLElementTagNameMap {
1032
- 'gs-lineage-filter': LineageFilterComponent;
1033
- }
1034
- interface HTMLElementEventMap {
1035
- [gsEventNames.lineageFilterChanged]: LineageFilterChangedEvent;
1036
- [gsEventNames.lineageFilterMultiChanged]: LineageMultiFilterChangedEvent;
1022
+ 'gs-relative-growth-advantage': RelativeGrowthAdvantageComponent;
1037
1023
  }
1038
1024
  }
1039
1025
 
@@ -1041,7 +1027,7 @@ declare global {
1041
1027
  declare global {
1042
1028
  namespace JSX {
1043
1029
  interface IntrinsicElements {
1044
- 'gs-lineage-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1030
+ 'gs-relative-growth-advantage': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1045
1031
  }
1046
1032
  }
1047
1033
  }
@@ -1049,10 +1035,7 @@ declare global {
1049
1035
 
1050
1036
  declare global {
1051
1037
  interface HTMLElementTagNameMap {
1052
- 'gs-mutation-filter': MutationFilterComponent;
1053
- }
1054
- interface HTMLElementEventMap {
1055
- [gsEventNames.mutationFilterChanged]: CustomEvent<MutationsFilter>;
1038
+ 'gs-prevalence-over-time': PrevalenceOverTimeComponent;
1056
1039
  }
1057
1040
  }
1058
1041
 
@@ -1060,7 +1043,7 @@ declare global {
1060
1043
  declare global {
1061
1044
  namespace JSX {
1062
1045
  interface IntrinsicElements {
1063
- 'gs-mutation-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1046
+ 'gs-prevalence-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1064
1047
  }
1065
1048
  }
1066
1049
  }
@@ -1068,11 +1051,7 @@ declare global {
1068
1051
 
1069
1052
  declare global {
1070
1053
  interface HTMLElementTagNameMap {
1071
- 'gs-number-range-filter': NumberRangeFilterComponent;
1072
- }
1073
- interface HTMLElementEventMap {
1074
- [gsEventNames.numberRangeFilterChanged]: NumberRangeFilterChangedEvent;
1075
- [gsEventNames.numberRangeValueChanged]: NumberRangeValueChangedEvent;
1054
+ 'gs-aggregate': AggregateComponent;
1076
1055
  }
1077
1056
  }
1078
1057
 
@@ -1080,7 +1059,7 @@ declare global {
1080
1059
  declare global {
1081
1060
  namespace JSX {
1082
1061
  interface IntrinsicElements {
1083
- 'gs-number-range-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1062
+ 'gs-aggregate': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1084
1063
  }
1085
1064
  }
1086
1065
  }
@@ -1088,7 +1067,7 @@ declare global {
1088
1067
 
1089
1068
  declare global {
1090
1069
  interface HTMLElementTagNameMap {
1091
- 'gs-genome-data-viewer': GenomeDataViewerComponent;
1070
+ 'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
1092
1071
  }
1093
1072
  }
1094
1073
 
@@ -1096,7 +1075,7 @@ declare global {
1096
1075
  declare global {
1097
1076
  namespace JSX {
1098
1077
  interface IntrinsicElements {
1099
- 'gs-genome-data-viewer': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1078
+ 'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1100
1079
  }
1101
1080
  }
1102
1081
  }
@@ -1104,7 +1083,7 @@ declare global {
1104
1083
 
1105
1084
  declare global {
1106
1085
  interface HTMLElementTagNameMap {
1107
- 'gs-mutation-comparison-component': MutationComparisonComponent;
1086
+ 'gs-statistics': StatisticsComponent;
1108
1087
  }
1109
1088
  }
1110
1089
 
@@ -1112,7 +1091,7 @@ declare global {
1112
1091
  declare global {
1113
1092
  namespace JSX {
1114
1093
  interface IntrinsicElements {
1115
- 'gs-mutation-comparison-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1094
+ 'gs-statistics': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1116
1095
  }
1117
1096
  }
1118
1097
  }
@@ -1120,7 +1099,7 @@ declare global {
1120
1099
 
1121
1100
  declare global {
1122
1101
  interface HTMLElementTagNameMap {
1123
- 'gs-mutations': MutationsComponent;
1102
+ 'gs-sequences-by-location': SequencesByLocationComponent;
1124
1103
  }
1125
1104
  }
1126
1105
 
@@ -1128,7 +1107,7 @@ declare global {
1128
1107
  declare global {
1129
1108
  namespace JSX {
1130
1109
  interface IntrinsicElements {
1131
- 'gs-mutations': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1110
+ 'gs-sequences-by-location': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1132
1111
  }
1133
1112
  }
1134
1113
  }
@@ -1136,7 +1115,7 @@ declare global {
1136
1115
 
1137
1116
  declare global {
1138
1117
  interface HTMLElementTagNameMap {
1139
- 'gs-prevalence-over-time': PrevalenceOverTimeComponent;
1118
+ 'gs-mutations-over-time': MutationsOverTimeComponent;
1140
1119
  }
1141
1120
  }
1142
1121
 
@@ -1144,7 +1123,7 @@ declare global {
1144
1123
  declare global {
1145
1124
  namespace JSX {
1146
1125
  interface IntrinsicElements {
1147
- 'gs-prevalence-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1126
+ 'gs-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1148
1127
  }
1149
1128
  }
1150
1129
  }
@@ -1152,7 +1131,11 @@ declare global {
1152
1131
 
1153
1132
  declare global {
1154
1133
  interface HTMLElementTagNameMap {
1155
- 'gs-relative-growth-advantage': RelativeGrowthAdvantageComponent;
1134
+ 'gs-date-range-filter': DateRangeFilterComponent;
1135
+ }
1136
+ interface HTMLElementEventMap {
1137
+ [gsEventNames.dateRangeFilterChanged]: CustomEvent<Record<string, string>>;
1138
+ [gsEventNames.dateRangeOptionChanged]: DateRangeOptionChangedEvent;
1156
1139
  }
1157
1140
  }
1158
1141
 
@@ -1160,7 +1143,7 @@ declare global {
1160
1143
  declare global {
1161
1144
  namespace JSX {
1162
1145
  interface IntrinsicElements {
1163
- 'gs-relative-growth-advantage': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1146
+ 'gs-date-range-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1164
1147
  }
1165
1148
  }
1166
1149
  }
@@ -1168,7 +1151,10 @@ declare global {
1168
1151
 
1169
1152
  declare global {
1170
1153
  interface HTMLElementTagNameMap {
1171
- 'gs-aggregate': AggregateComponent;
1154
+ 'gs-text-filter': TextFilterComponent;
1155
+ }
1156
+ interface HTMLElementEventMap {
1157
+ [gsEventNames.textFilterChanged]: TextFilterChangedEvent;
1172
1158
  }
1173
1159
  }
1174
1160
 
@@ -1176,7 +1162,7 @@ declare global {
1176
1162
  declare global {
1177
1163
  namespace JSX {
1178
1164
  interface IntrinsicElements {
1179
- 'gs-aggregate': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1165
+ 'gs-text-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1180
1166
  }
1181
1167
  }
1182
1168
  }
@@ -1184,7 +1170,10 @@ declare global {
1184
1170
 
1185
1171
  declare global {
1186
1172
  interface HTMLElementTagNameMap {
1187
- 'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
1173
+ 'gs-location-filter': LocationFilterComponent;
1174
+ }
1175
+ interface HTMLElementEventMap {
1176
+ [gsEventNames.locationChanged]: LocationChangedEvent;
1188
1177
  }
1189
1178
  }
1190
1179
 
@@ -1192,7 +1181,7 @@ declare global {
1192
1181
  declare global {
1193
1182
  namespace JSX {
1194
1183
  interface IntrinsicElements {
1195
- 'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1184
+ 'gs-location-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1196
1185
  }
1197
1186
  }
1198
1187
  }
@@ -1200,7 +1189,10 @@ declare global {
1200
1189
 
1201
1190
  declare global {
1202
1191
  interface HTMLElementTagNameMap {
1203
- 'gs-mutations-over-time': MutationsOverTimeComponent;
1192
+ 'gs-mutation-filter': MutationFilterComponent;
1193
+ }
1194
+ interface HTMLElementEventMap {
1195
+ [gsEventNames.mutationFilterChanged]: CustomEvent<MutationsFilter>;
1204
1196
  }
1205
1197
  }
1206
1198
 
@@ -1208,7 +1200,7 @@ declare global {
1208
1200
  declare global {
1209
1201
  namespace JSX {
1210
1202
  interface IntrinsicElements {
1211
- 'gs-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1203
+ 'gs-mutation-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1212
1204
  }
1213
1205
  }
1214
1206
  }
@@ -1216,7 +1208,11 @@ declare global {
1216
1208
 
1217
1209
  declare global {
1218
1210
  interface HTMLElementTagNameMap {
1219
- 'gs-sequences-by-location': SequencesByLocationComponent;
1211
+ 'gs-number-range-filter': NumberRangeFilterComponent;
1212
+ }
1213
+ interface HTMLElementEventMap {
1214
+ [gsEventNames.numberRangeFilterChanged]: NumberRangeFilterChangedEvent;
1215
+ [gsEventNames.numberRangeValueChanged]: NumberRangeValueChangedEvent;
1220
1216
  }
1221
1217
  }
1222
1218
 
@@ -1224,7 +1220,7 @@ declare global {
1224
1220
  declare global {
1225
1221
  namespace JSX {
1226
1222
  interface IntrinsicElements {
1227
- 'gs-sequences-by-location': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1223
+ 'gs-number-range-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1228
1224
  }
1229
1225
  }
1230
1226
  }
@@ -1232,7 +1228,11 @@ declare global {
1232
1228
 
1233
1229
  declare global {
1234
1230
  interface HTMLElementTagNameMap {
1235
- 'gs-statistics': StatisticsComponent;
1231
+ 'gs-lineage-filter': LineageFilterComponent;
1232
+ }
1233
+ interface HTMLElementEventMap {
1234
+ [gsEventNames.lineageFilterChanged]: LineageFilterChangedEvent;
1235
+ [gsEventNames.lineageFilterMultiChanged]: LineageMultiFilterChangedEvent;
1236
1236
  }
1237
1237
  }
1238
1238
 
@@ -1240,7 +1240,7 @@ declare global {
1240
1240
  declare global {
1241
1241
  namespace JSX {
1242
1242
  interface IntrinsicElements {
1243
- 'gs-statistics': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1243
+ 'gs-lineage-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1244
1244
  }
1245
1245
  }
1246
1246
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genspectrum/dashboard-components",
3
- "version": "1.11.0",
3
+ "version": "1.12.0",
4
4
  "description": "GenSpectrum web components for building dashboards",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-only",
@@ -62,6 +62,7 @@ export const mutationsOverTimeResponse = makeLapisResponse(
62
62
  }),
63
63
  ),
64
64
  ),
65
+ totalCountsByDateRange: z.array(z.number()),
65
66
  }),
66
67
  );
67
68
  export type MutationsOverTimeResponse = z.infer<typeof mutationsOverTimeResponse>;
@@ -68,7 +68,7 @@ export function getFilteredMutationOverTimeData({
68
68
  if (hideGaps) {
69
69
  const dateRangesToFilterOut = filteredData.getSecondAxisKeys().filter((dateRange) => {
70
70
  const vals = filteredData.getColumn(dateRange);
71
- return !vals.some((v) => v?.type === 'value' && v.totalCount > 0);
71
+ return !vals.some((v) => (v?.type === 'value' || v?.type === 'valueWithCoverage') && v.totalCount > 0);
72
72
  });
73
73
  dateRangesToFilterOut.forEach((dateRange) => filteredData.deleteColumn(dateRange));
74
74
  }
@@ -65,6 +65,53 @@ export const WithValue: StoryObj<MutationsOverTimeGridTooltipProps> = {
65
65
  play: async ({ canvasElement }) => {
66
66
  const canvas = within(canvasElement);
67
67
 
68
+ await expect(canvas.getByText('50.00%')).toBeVisible();
69
+ await expect(canvas.getByText('100')).toBeVisible();
70
+ await expect(canvas.getByText('have the mutation A500-.')).toBeVisible();
71
+ await expect(canvas.getByText('200')).toBeVisible();
72
+ await expect(canvas.getByText('have coverage at position 500.')).toBeVisible();
73
+ await expect(canvas.getByText('300')).toBeVisible();
74
+ await expect(canvas.getByText('total in this date range.')).toBeVisible();
75
+ },
76
+ };
77
+
78
+ export const WithValueWithZero: StoryObj<MutationsOverTimeGridTooltipProps> = {
79
+ ...Template,
80
+ args: {
81
+ ...Template.args,
82
+ value: {
83
+ type: 'value',
84
+ proportion: 0,
85
+ count: 0,
86
+ totalCount: 300,
87
+ },
88
+ },
89
+ play: async ({ canvasElement }) => {
90
+ const canvas = within(canvasElement);
91
+
92
+ await expect(canvas.getByText('0.00%')).toBeVisible();
93
+ await expect(canvas.getByText('0')).toBeVisible();
94
+ await expect(canvas.getByText('have the mutation A500-.')).toBeVisible();
95
+ await expect(canvas.queryByText('with coverage at position 500.')).not.toBeInTheDocument();
96
+ await expect(canvas.getByText('300')).toBeVisible();
97
+ await expect(canvas.getByText('total in this date range.')).toBeVisible();
98
+ },
99
+ };
100
+
101
+ export const WithValueWithCoverage: StoryObj<MutationsOverTimeGridTooltipProps> = {
102
+ ...Template,
103
+ args: {
104
+ ...Template.args,
105
+ value: {
106
+ type: 'valueWithCoverage',
107
+ count: 100,
108
+ coverage: 200,
109
+ totalCount: 300,
110
+ },
111
+ },
112
+ play: async ({ canvasElement }) => {
113
+ const canvas = within(canvasElement);
114
+
68
115
  await expect(canvas.getByText('50.00%')).toBeVisible();
69
116
  await expect(canvas.getByText('100')).toBeVisible();
70
117
  await expect(canvas.getByText('have the mutation A500- out of')).toBeVisible();
@@ -21,12 +21,26 @@ export const MutationsOverTimeGridTooltip: FunctionComponent<MutationsOverTimeGr
21
21
  }: MutationsOverTimeGridTooltipProps) => {
22
22
  const dateClass = toTemporalClass(date);
23
23
 
24
- const proportionText =
25
- value === null
26
- ? 'No data'
27
- : value.type === 'belowThreshold'
28
- ? `<${formatProportion(MUTATIONS_OVER_TIME_MIN_PROPORTION)}`
29
- : formatProportion(value.proportion);
24
+ let proportionText = 'No data';
25
+
26
+ if (value !== null) {
27
+ switch (value.type) {
28
+ case 'belowThreshold': {
29
+ proportionText = `<${formatProportion(MUTATIONS_OVER_TIME_MIN_PROPORTION)}`;
30
+ break;
31
+ }
32
+ case 'value':
33
+ case 'wastewaterValue': {
34
+ proportionText = formatProportion(value.proportion);
35
+ break;
36
+ }
37
+ case 'valueWithCoverage': {
38
+ // value.coverage will always be non-zero if we're in this case
39
+ proportionText = formatProportion(value.count / value.coverage);
40
+ break;
41
+ }
42
+ }
43
+ }
30
44
 
31
45
  return (
32
46
  <div>
@@ -61,21 +75,50 @@ const TooltipValueCountsDescription: FunctionComponent<{
61
75
  }
62
76
  return (
63
77
  <div className='mt-2'>
64
- {value.type === 'belowThreshold' ? (
65
- <p className='text-gray-600'>
66
- None or less than {formatProportion(MUTATIONS_OVER_TIME_MIN_PROPORTION)} have the mutation.
67
- </p>
68
- ) : (
69
- <>
70
- <p>
71
- {value.count} <span className='text-gray-600'>have the mutation {mutationCode} out of</span>
72
- </p>
73
- <p>
74
- {totalCountWithCoverage(value.count, value.proportion)}{' '}
75
- <span className='text-gray-600'>with coverage at position {mutationPosition}.</span>
76
- </p>
77
- </>
78
- )}
78
+ {(() => {
79
+ switch (value.type) {
80
+ case 'belowThreshold':
81
+ return (
82
+ <p className='text-gray-600'>
83
+ None or less than {formatProportion(MUTATIONS_OVER_TIME_MIN_PROPORTION)} have the
84
+ mutation.
85
+ </p>
86
+ );
87
+
88
+ case 'value':
89
+ return (
90
+ <>
91
+ <p>
92
+ {value.count}{' '}
93
+ <span className='text-gray-600'>have the mutation {mutationCode}.</span>
94
+ </p>
95
+ {value.proportion > 0 && (
96
+ <p>
97
+ {Math.round(value.count / value.proportion)}{' '}
98
+ <span className='text-gray-600'>
99
+ have coverage at position {mutationPosition}.
100
+ </span>
101
+ </p>
102
+ )}
103
+ </>
104
+ );
105
+
106
+ case 'valueWithCoverage':
107
+ return (
108
+ <>
109
+ <p>
110
+ {value.count}{' '}
111
+ <span className='text-gray-600'>have the mutation {mutationCode} out of</span>
112
+ </p>
113
+ <p>
114
+ {value.coverage}{' '}
115
+ <span className='text-gray-600'>with coverage at position {mutationPosition}.</span>
116
+ </p>
117
+ </>
118
+ );
119
+ }
120
+ })()}
121
+
79
122
  <p>
80
123
  {value.totalCount} <span className='text-gray-600'>total in this date range.</span>
81
124
  </p>
@@ -83,13 +126,6 @@ const TooltipValueCountsDescription: FunctionComponent<{
83
126
  );
84
127
  };
85
128
 
86
- function totalCountWithCoverage(count: number, proportion: number) {
87
- if (count === 0) {
88
- return 0;
89
- }
90
- return Math.round(count / proportion);
91
- }
92
-
93
129
  const timeIntervalDisplay = (date: TemporalClass) => {
94
130
  if (date instanceof YearMonthDayClass) {
95
131
  return date.toString();
@@ -4,7 +4,7 @@ import z from 'zod';
4
4
 
5
5
  import { type MutationOverTimeDataMap } from './MutationOverTimeData';
6
6
  import { MutationsOverTimeGridTooltip } from './mutations-over-time-grid-tooltip';
7
- import { type MutationOverTimeMutationValue } from '../../query/queryMutationsOverTime';
7
+ import { getProportion, type MutationOverTimeMutationValue } from '../../query/queryMutationsOverTime';
8
8
  import { type SequenceType } from '../../types';
9
9
  import { type Deletion, type Substitution } from '../../utils/mutations';
10
10
  import { type Temporal } from '../../utils/temporalClass';
@@ -211,7 +211,7 @@ const ProportionCell: FunctionComponent<{
211
211
  colorScale: ColorScale;
212
212
  tooltipPortalTarget: HTMLElement | null;
213
213
  }> = ({ value, mutation, date, tooltipPosition, colorScale, tooltipPortalTarget }) => {
214
- const proportion = value?.type === 'belowThreshold' ? undefined : value?.proportion;
214
+ const proportion = getProportion(value);
215
215
 
216
216
  return (
217
217
  <div className={'py-1 w-full h-full'}>
@@ -12,7 +12,7 @@ import {
12
12
  } from './getFilteredMutationsOverTimeData';
13
13
  import { type MutationOverTimeWorkerResponse } from './mutationOverTimeWorker';
14
14
  import MutationsOverTimeGrid, { customColumnSchema } from './mutations-over-time-grid';
15
- import { type MutationOverTimeQuery } from '../../query/queryMutationsOverTime';
15
+ import { getProportion, type MutationOverTimeQuery } from '../../query/queryMutationsOverTime';
16
16
  import {
17
17
  lapisFilterSchema,
18
18
  sequenceTypeSchema,
@@ -356,7 +356,7 @@ function getDownloadData(filteredData: MutationOverTimeDataMap) {
356
356
  return dates.reduce(
357
357
  (accumulated, date) => {
358
358
  const value = filteredData.get(mutation, date);
359
- const proportion = value?.type === 'value' || value?.type === 'wastewaterValue' ? value.proportion : '';
359
+ const proportion = getProportion(value ?? null) ?? '';
360
360
  return {
361
361
  ...accumulated,
362
362
  [date.dateString]: proportion,
@@ -49,6 +49,12 @@ export type MutationOverTimeMutationValue =
49
49
  count: number;
50
50
  totalCount: number;
51
51
  }
52
+ | {
53
+ type: 'valueWithCoverage';
54
+ count: number;
55
+ coverage: number;
56
+ totalCount: number;
57
+ }
52
58
  | {
53
59
  type: 'wastewaterValue';
54
60
  proportion: number;
@@ -59,6 +65,19 @@ export type MutationOverTimeMutationValue =
59
65
  }
60
66
  | null;
61
67
 
68
+ export function getProportion(value: MutationOverTimeMutationValue) {
69
+ switch (value?.type) {
70
+ case 'value':
71
+ case 'wastewaterValue':
72
+ return value.proportion;
73
+ case 'valueWithCoverage':
74
+ return value.count / value.coverage;
75
+ case 'belowThreshold':
76
+ return undefined;
77
+ }
78
+ return undefined;
79
+ }
80
+
62
81
  const MAX_NUMBER_OF_GRID_COLUMNS = 200;
63
82
  export const MUTATIONS_OVER_TIME_MIN_PROPORTION = 0.001;
64
83
 
@@ -237,19 +256,6 @@ async function queryMutationsOverTimeDataDirectEndpoint(
237
256
  }> {
238
257
  const overallMutationData = await overallMutationDataPromise;
239
258
  overallMutationData.sort((a, b) => sortSubstitutionsAndDeletions(a.mutation, b.mutation));
240
- const totalCounts = await Promise.all(
241
- allDates.map(async (date) => {
242
- const filter = {
243
- ...lapisFilter,
244
- [`${lapisDateField}From`]: date.firstDay.toString(),
245
- [`${lapisDateField}To`]: date.lastDay.toString(),
246
- };
247
-
248
- const totalCountQuery = await getTotalNumberOfSequencesInDateRange(filter).evaluate(lapis, signal);
249
-
250
- return totalCountQuery.content[0].count;
251
- }),
252
- );
253
259
 
254
260
  const includeMutations = overallMutationData.map((value) => value.mutation.code);
255
261
  const apiResult = await fetchMutationsOverTime(
@@ -267,6 +273,7 @@ async function queryMutationsOverTimeDataDirectEndpoint(
267
273
  signal,
268
274
  );
269
275
 
276
+ const totalCounts = apiResult.data.totalCountsByDateRange;
270
277
  const responseMutations = apiResult.data.mutations.map(parseMutationCode);
271
278
  const mutationEntries: SubstitutionOrDeletionEntry[] = responseMutations.map((mutation, i) => {
272
279
  const numbers = {
@@ -316,9 +323,9 @@ async function queryMutationsOverTimeDataDirectEndpoint(
316
323
  return [
317
324
  date.dateString,
318
325
  {
319
- type: 'value',
320
- proportion: count / coverage,
326
+ type: 'valueWithCoverage',
321
327
  count,
328
+ coverage,
322
329
  totalCount,
323
330
  },
324
331
  ];