@genspectrum/dashboard-components 0.7.1 → 0.8.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.
@@ -450,16 +450,6 @@ export declare class MutationComparisonComponent extends PreactLitAdapterWithGri
450
450
  * Fired when:
451
451
  * - The user has submitted a valid mutation or insertion
452
452
  * - The user has removed a mutation or insertion
453
- *
454
- * @fires {CustomEvent<{
455
- * nucleotideMutations: string[],
456
- * aminoAcidMutations: string[],
457
- * nucleotideInsertions: string[],
458
- * aminoAcidInsertions: string[]
459
- * }>} gs-mutation-filter-on-blur
460
- * Fired when:
461
- * - the mutation filter has lost focus
462
- * Contains the selected mutations in the format
463
453
  */
464
454
  export declare class MutationFilterComponent extends PreactLitAdapter {
465
455
  /**
@@ -1039,10 +1029,7 @@ declare global {
1039
1029
 
1040
1030
  declare global {
1041
1031
  interface HTMLElementTagNameMap {
1042
- 'gs-date-range-selector': DateRangeSelectorComponent;
1043
- }
1044
- interface HTMLElementEventMap {
1045
- 'gs-date-range-changed': CustomEvent<Record<string, string>>;
1032
+ 'gs-mutation-comparison-component': MutationComparisonComponent;
1046
1033
  }
1047
1034
  }
1048
1035
 
@@ -1050,7 +1037,7 @@ declare global {
1050
1037
  declare global {
1051
1038
  namespace JSX {
1052
1039
  interface IntrinsicElements {
1053
- 'gs-date-range-selector': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1040
+ 'gs-mutation-comparison-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1054
1041
  }
1055
1042
  }
1056
1043
  }
@@ -1058,10 +1045,7 @@ declare global {
1058
1045
 
1059
1046
  declare global {
1060
1047
  interface HTMLElementTagNameMap {
1061
- 'gs-text-input': TextInputComponent;
1062
- }
1063
- interface HTMLElementEventMap {
1064
- 'gs-text-input-changed': CustomEvent<Record<string, string>>;
1048
+ 'gs-mutations-component': MutationsComponent;
1065
1049
  }
1066
1050
  }
1067
1051
 
@@ -1069,7 +1053,7 @@ declare global {
1069
1053
  declare global {
1070
1054
  namespace JSX {
1071
1055
  interface IntrinsicElements {
1072
- 'gs-text-input': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1056
+ 'gs-mutations-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1073
1057
  }
1074
1058
  }
1075
1059
  }
@@ -1077,10 +1061,7 @@ declare global {
1077
1061
 
1078
1062
  declare global {
1079
1063
  interface HTMLElementTagNameMap {
1080
- 'gs-location-filter': LocationFilterComponent;
1081
- }
1082
- interface HTMLElementEventMap {
1083
- 'gs-location-changed': CustomEvent<Record<string, string>>;
1064
+ 'gs-prevalence-over-time': PrevalenceOverTimeComponent;
1084
1065
  }
1085
1066
  }
1086
1067
 
@@ -1088,7 +1069,7 @@ declare global {
1088
1069
  declare global {
1089
1070
  namespace JSX {
1090
1071
  interface IntrinsicElements {
1091
- 'gs-location-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1072
+ 'gs-prevalence-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1092
1073
  }
1093
1074
  }
1094
1075
  }
@@ -1096,11 +1077,7 @@ declare global {
1096
1077
 
1097
1078
  declare global {
1098
1079
  interface HTMLElementTagNameMap {
1099
- 'gs-mutation-filter': MutationFilterComponent;
1100
- }
1101
- interface HTMLElementEventMap {
1102
- 'gs-mutation-filter-changed': CustomEvent<SelectedMutationFilterStrings>;
1103
- 'gs-mutation-filter-on-blur': CustomEvent<SelectedMutationFilterStrings>;
1080
+ 'gs-relative-growth-advantage': RelativeGrowthAdvantageComponent;
1104
1081
  }
1105
1082
  }
1106
1083
 
@@ -1108,7 +1085,7 @@ declare global {
1108
1085
  declare global {
1109
1086
  namespace JSX {
1110
1087
  interface IntrinsicElements {
1111
- 'gs-mutation-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1088
+ 'gs-relative-growth-advantage': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1112
1089
  }
1113
1090
  }
1114
1091
  }
@@ -1116,10 +1093,7 @@ declare global {
1116
1093
 
1117
1094
  declare global {
1118
1095
  interface HTMLElementTagNameMap {
1119
- 'gs-lineage-filter': LineageFilterComponent;
1120
- }
1121
- interface HTMLElementEventMap {
1122
- 'gs-lineage-filter-changed': CustomEvent<Record<string, string>>;
1096
+ 'gs-aggregate-component': AggregateComponent;
1123
1097
  }
1124
1098
  }
1125
1099
 
@@ -1127,7 +1101,7 @@ declare global {
1127
1101
  declare global {
1128
1102
  namespace JSX {
1129
1103
  interface IntrinsicElements {
1130
- 'gs-lineage-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1104
+ 'gs-aggregate-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1131
1105
  }
1132
1106
  }
1133
1107
  }
@@ -1135,7 +1109,7 @@ declare global {
1135
1109
 
1136
1110
  declare global {
1137
1111
  interface HTMLElementTagNameMap {
1138
- 'gs-mutation-comparison-component': MutationComparisonComponent;
1112
+ 'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
1139
1113
  }
1140
1114
  }
1141
1115
 
@@ -1143,7 +1117,7 @@ declare global {
1143
1117
  declare global {
1144
1118
  namespace JSX {
1145
1119
  interface IntrinsicElements {
1146
- 'gs-mutation-comparison-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1120
+ 'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1147
1121
  }
1148
1122
  }
1149
1123
  }
@@ -1151,7 +1125,7 @@ declare global {
1151
1125
 
1152
1126
  declare global {
1153
1127
  interface HTMLElementTagNameMap {
1154
- 'gs-mutations-component': MutationsComponent;
1128
+ 'gs-mutations-over-time': MutationsOverTimeComponent;
1155
1129
  }
1156
1130
  }
1157
1131
 
@@ -1159,7 +1133,7 @@ declare global {
1159
1133
  declare global {
1160
1134
  namespace JSX {
1161
1135
  interface IntrinsicElements {
1162
- 'gs-mutations-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1136
+ 'gs-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1163
1137
  }
1164
1138
  }
1165
1139
  }
@@ -1167,7 +1141,10 @@ declare global {
1167
1141
 
1168
1142
  declare global {
1169
1143
  interface HTMLElementTagNameMap {
1170
- 'gs-prevalence-over-time': PrevalenceOverTimeComponent;
1144
+ 'gs-date-range-selector': DateRangeSelectorComponent;
1145
+ }
1146
+ interface HTMLElementEventMap {
1147
+ 'gs-date-range-changed': CustomEvent<Record<string, string>>;
1171
1148
  }
1172
1149
  }
1173
1150
 
@@ -1175,7 +1152,7 @@ declare global {
1175
1152
  declare global {
1176
1153
  namespace JSX {
1177
1154
  interface IntrinsicElements {
1178
- 'gs-prevalence-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1155
+ 'gs-date-range-selector': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1179
1156
  }
1180
1157
  }
1181
1158
  }
@@ -1183,7 +1160,10 @@ declare global {
1183
1160
 
1184
1161
  declare global {
1185
1162
  interface HTMLElementTagNameMap {
1186
- 'gs-aggregate-component': AggregateComponent;
1163
+ 'gs-location-filter': LocationFilterComponent;
1164
+ }
1165
+ interface HTMLElementEventMap {
1166
+ 'gs-location-changed': CustomEvent<Record<string, string>>;
1187
1167
  }
1188
1168
  }
1189
1169
 
@@ -1191,7 +1171,7 @@ declare global {
1191
1171
  declare global {
1192
1172
  namespace JSX {
1193
1173
  interface IntrinsicElements {
1194
- 'gs-aggregate-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1174
+ 'gs-location-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1195
1175
  }
1196
1176
  }
1197
1177
  }
@@ -1199,7 +1179,10 @@ declare global {
1199
1179
 
1200
1180
  declare global {
1201
1181
  interface HTMLElementTagNameMap {
1202
- 'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
1182
+ 'gs-text-input': TextInputComponent;
1183
+ }
1184
+ interface HTMLElementEventMap {
1185
+ 'gs-text-input-changed': CustomEvent<Record<string, string>>;
1203
1186
  }
1204
1187
  }
1205
1188
 
@@ -1207,7 +1190,7 @@ declare global {
1207
1190
  declare global {
1208
1191
  namespace JSX {
1209
1192
  interface IntrinsicElements {
1210
- 'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1193
+ 'gs-text-input': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1211
1194
  }
1212
1195
  }
1213
1196
  }
@@ -1215,7 +1198,10 @@ declare global {
1215
1198
 
1216
1199
  declare global {
1217
1200
  interface HTMLElementTagNameMap {
1218
- 'gs-mutations-over-time': MutationsOverTimeComponent;
1201
+ 'gs-mutation-filter': MutationFilterComponent;
1202
+ }
1203
+ interface HTMLElementEventMap {
1204
+ 'gs-mutation-filter-changed': CustomEvent<SelectedMutationFilterStrings>;
1219
1205
  }
1220
1206
  }
1221
1207
 
@@ -1223,7 +1209,7 @@ declare global {
1223
1209
  declare global {
1224
1210
  namespace JSX {
1225
1211
  interface IntrinsicElements {
1226
- 'gs-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1212
+ 'gs-mutation-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1227
1213
  }
1228
1214
  }
1229
1215
  }
@@ -1231,7 +1217,10 @@ declare global {
1231
1217
 
1232
1218
  declare global {
1233
1219
  interface HTMLElementTagNameMap {
1234
- 'gs-relative-growth-advantage': RelativeGrowthAdvantageComponent;
1220
+ 'gs-lineage-filter': LineageFilterComponent;
1221
+ }
1222
+ interface HTMLElementEventMap {
1223
+ 'gs-lineage-filter-changed': CustomEvent<Record<string, string>>;
1235
1224
  }
1236
1225
  }
1237
1226
 
@@ -1239,7 +1228,7 @@ declare global {
1239
1228
  declare global {
1240
1229
  namespace JSX {
1241
1230
  interface IntrinsicElements {
1242
- 'gs-relative-growth-advantage': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1231
+ 'gs-lineage-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1243
1232
  }
1244
1233
  }
1245
1234
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genspectrum/dashboard-components",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "description": "GenSpectrum web components for building dashboards",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-only",
@@ -111,7 +111,7 @@
111
111
  "eslint-config-preact": "^1.3.0",
112
112
  "eslint-plugin-import": "^2.29.1",
113
113
  "eslint-plugin-jest": "^28.2.0",
114
- "eslint-plugin-storybook": "^0.10.1",
114
+ "eslint-plugin-storybook": "^0.11.0",
115
115
  "http-server": "^14.1.1",
116
116
  "lit-analyzer": "^2.0.3",
117
117
  "msw": "^2.2.14",
@@ -14,6 +14,9 @@ export class SlidingOperator<Data, AggregationResult> implements Operator<Aggreg
14
14
  async evaluate(lapis: string, signal?: AbortSignal) {
15
15
  const childEvaluated = await this.child.evaluate(lapis, signal);
16
16
  const content = new Array<AggregationResult>();
17
+ if (childEvaluated.content.length === 0) {
18
+ return { content };
19
+ }
17
20
  const numberOfWindows = Math.max(childEvaluated.content.length - this.windowSize, 0) + 1;
18
21
  for (let i = 0; i < numberOfWindows; i++) {
19
22
  content.push(this.aggregate(childEvaluated.content.slice(i, i + this.windowSize)));
@@ -25,6 +25,7 @@ export class UserFacingError extends Error {
25
25
  }
26
26
 
27
27
  export const ErrorDisplay: FunctionComponent<{ error: Error }> = ({ error }) => {
28
+ // eslint-disable-next-line no-console -- Currently we use the following statement for our error handling
28
29
  console.error(error);
29
30
 
30
31
  const containerRef = useRef<HTMLInputElement>(null);
@@ -14,7 +14,7 @@ const meta: Meta<MutationFilterProps> = {
14
14
  component: MutationFilter,
15
15
  parameters: {
16
16
  actions: {
17
- handles: ['gs-mutation-filter-changed', 'gs-mutation-filter-on-blur', ...previewHandles],
17
+ handles: ['gs-mutation-filter-changed', ...previewHandles],
18
18
  },
19
19
  fetchMock: {},
20
20
  },
@@ -120,32 +120,6 @@ export const FiresFilterChangedEvents: StoryObj<MutationFilterProps> = {
120
120
  },
121
121
  };
122
122
 
123
- export const FiresFilterOnBlurEvent: StoryObj<MutationFilterProps> = {
124
- ...Default,
125
- play: async ({ canvasElement, step }) => {
126
- const { canvas, onBlurListenerMock } = await prepare(canvasElement, step);
127
-
128
- await step('Move outside of input', async () => {
129
- await submitMutation(canvas, 'A234T');
130
- await submitMutation(canvas, 'S:A123G');
131
- await submitMutation(canvas, 'ins_123:AAA');
132
- await submitMutation(canvas, 'ins_S:123:AAA');
133
- await userEvent.tab();
134
-
135
- await expect(onBlurListenerMock).toHaveBeenCalledWith(
136
- expect.objectContaining({
137
- detail: {
138
- nucleotideMutations: ['A234T'],
139
- aminoAcidMutations: ['S:A123G'],
140
- nucleotideInsertions: ['ins_123:AAA'],
141
- aminoAcidInsertions: ['ins_S:123:AAA'],
142
- },
143
- }),
144
- );
145
- });
146
- },
147
- };
148
-
149
123
  export const WithInitialValue: StoryObj<MutationFilterProps> = {
150
124
  render: (args) => (
151
125
  <LapisUrlContext.Provider value={LAPIS_URL}>
@@ -164,13 +138,12 @@ export const WithInitialValue: StoryObj<MutationFilterProps> = {
164
138
  width: '100%',
165
139
  },
166
140
  play: async ({ canvasElement, step }) => {
167
- const { canvas, onBlurListenerMock } = await prepare(canvasElement, step);
141
+ const { canvas, changedListenerMock } = await prepare(canvasElement, step);
168
142
 
169
- await step('Move outside of input', async () => {
143
+ await step('Enter additional input', async () => {
170
144
  await submitMutation(canvas, 'G500T');
171
- await userEvent.tab();
172
145
 
173
- await expect(onBlurListenerMock).toHaveBeenCalledWith(
146
+ await expect(changedListenerMock).toHaveBeenCalledWith(
174
147
  expect.objectContaining({
175
148
  detail: {
176
149
  nucleotideMutations: ['A234T', 'G500T'],
@@ -187,10 +160,8 @@ export const WithInitialValue: StoryObj<MutationFilterProps> = {
187
160
  async function prepare(canvasElement: HTMLElement, step: StepFunction<PreactRenderer, unknown>) {
188
161
  const canvas = within(canvasElement);
189
162
 
190
- const onBlurListenerMock = fn();
191
163
  const changedListenerMock = fn();
192
164
  await step('Setup event listener mock', async () => {
193
- canvasElement.addEventListener('gs-mutation-filter-on-blur', onBlurListenerMock);
194
165
  canvasElement.addEventListener('gs-mutation-filter-changed', changedListenerMock);
195
166
  });
196
167
 
@@ -200,7 +171,7 @@ async function prepare(canvasElement: HTMLElement, step: StepFunction<PreactRend
200
171
  });
201
172
  });
202
173
 
203
- return { canvas, onBlurListenerMock, changedListenerMock };
174
+ return { canvas, changedListenerMock };
204
175
  }
205
176
 
206
177
  const submitMutation = async (canvas: ReturnType<typeof within>, mutation: string) => {
@@ -87,18 +87,6 @@ export const MutationFilterInner: FunctionComponent<MutationFilterInnerProps> =
87
87
  );
88
88
  };
89
89
 
90
- const handleOnBlur = () => {
91
- const detail = mapToMutationFilterStrings(selectedFilters);
92
-
93
- formRef.current?.dispatchEvent(
94
- new CustomEvent<SelectedMutationFilterStrings>('gs-mutation-filter-on-blur', {
95
- detail,
96
- bubbles: true,
97
- composed: true,
98
- }),
99
- );
100
- };
101
-
102
90
  const handleInputChange = (event: Event) => {
103
91
  setInputValue((event.target as HTMLInputElement).value);
104
92
  setIsError(false);
@@ -124,7 +112,6 @@ export const MutationFilterInner: FunctionComponent<MutationFilterInnerProps> =
124
112
  value={inputValue}
125
113
  onInput={handleInputChange}
126
114
  placeholder={getPlaceholder(referenceGenome)}
127
- onBlur={handleOnBlur}
128
115
  />
129
116
  <button type='submit' className='btn btn-xs m-1'>
130
117
  +
@@ -63,15 +63,21 @@ export const MutationsOverTimeInner: FunctionComponent<MutationsOverTimeProps> =
63
63
  const lapis = useContext(LapisUrlContext);
64
64
  const { lapisFilter, sequenceType, granularity, lapisDateField } = componentProps;
65
65
 
66
- const { data, error, isLoading } = useWebWorker<MutationOverTimeQuery, MutationOverTimeWorkerResponse>(
67
- {
66
+ const messageToWorker = useMemo(() => {
67
+ return {
68
68
  lapisFilter,
69
69
  sequenceType,
70
70
  granularity,
71
71
  lapisDateField,
72
72
  lapis,
73
- },
74
- new MutationOverTimeWorker() as Worker,
73
+ };
74
+ }, [granularity, lapis, lapisDateField, lapisFilter, sequenceType]);
75
+
76
+ const worker = useMemo(() => new MutationOverTimeWorker(), []);
77
+
78
+ const { data, error, isLoading } = useWebWorker<MutationOverTimeQuery, MutationOverTimeWorkerResponse>(
79
+ messageToWorker,
80
+ worker,
75
81
  );
76
82
 
77
83
  if (isLoading) {
@@ -0,0 +1,11 @@
1
+ {
2
+ "errors": [],
3
+ "info": {
4
+ "apiVersion": 1,
5
+ "dataVersion": 1709685650,
6
+ "deprecationDate": null,
7
+ "deprecationInfo": null,
8
+ "acknowledgement": null
9
+ },
10
+ "data": []
11
+ }
@@ -9,6 +9,7 @@ import {
9
9
  } from '../../query/queryPrevalenceOverTime';
10
10
  import { sortNullToBeginningThenByDate } from '../../utils/sort';
11
11
  import GsChart from '../components/chart';
12
+ import { NoDataDisplay } from '../components/no-data-display';
12
13
  import { LogitScale } from '../shared/charts/LogitScale';
13
14
  import { singleGraphColorRGBAById } from '../shared/charts/colors';
14
15
  import { type ConfidenceIntervalMethod, wilson95PercentConfidenceInterval } from '../shared/charts/confideceInterval';
@@ -30,12 +31,18 @@ const PrevalenceOverTimeBarChart = ({
30
31
  confidenceIntervalMethod,
31
32
  yAxisMaxConfig,
32
33
  }: PrevalenceOverTimeBarChartProps) => {
33
- const nullFirstData = data.map((variantData) => {
34
- return {
35
- content: variantData.content.sort(sortNullToBeginningThenByDate),
36
- displayName: variantData.displayName,
37
- };
38
- });
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
+ });
42
+
43
+ if (nullFirstData.length === 0) {
44
+ return <NoDataDisplay />;
45
+ }
39
46
 
40
47
  const datasets = nullFirstData.map((graphData, index) => getDataset(graphData, index, confidenceIntervalMethod));
41
48
 
@@ -5,6 +5,7 @@ import { type PrevalenceOverTimeData } from '../../query/queryPrevalenceOverTime
5
5
  import { addUnit, minusTemporal } from '../../utils/temporalClass';
6
6
  import { getMinMaxNumber } from '../../utils/utils';
7
7
  import GsChart from '../components/chart';
8
+ import { NoDataDisplay } from '../components/no-data-display';
8
9
  import { LogitScale } from '../shared/charts/LogitScale';
9
10
  import { singleGraphColorRGBAById } from '../shared/charts/colors';
10
11
  import { getYAxisMax, type YAxisMaxConfig } from '../shared/charts/getYAxisMax';
@@ -23,12 +24,18 @@ const PrevalenceOverTimeBubbleChart = ({
23
24
  yAxisScaleType,
24
25
  yAxisMaxConfig,
25
26
  }: PrevalenceOverTimeBubbleChartProps) => {
26
- const nonNullDateRangeData = data.map((variantData) => {
27
- return {
28
- content: variantData.content.filter((dataPoint) => dataPoint.dateRange !== null),
29
- displayName: variantData.displayName,
30
- };
31
- });
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
+ });
35
+
36
+ if (nonNullDateRangeData.length === 0) {
37
+ return <NoDataDisplay />;
38
+ }
32
39
 
33
40
  const firstDate = nonNullDateRangeData[0].content[0].dateRange!;
34
41
  const total = nonNullDateRangeData.map((graphData) => graphData.content.map((dataPoint) => dataPoint.total)).flat();
@@ -4,6 +4,7 @@ import { type TooltipItem } from 'chart.js/dist/types';
4
4
  import { maxInData } from './prevalence-over-time';
5
5
  import { type PrevalenceOverTimeData, type PrevalenceOverTimeVariantData } from '../../query/queryPrevalenceOverTime';
6
6
  import GsChart from '../components/chart';
7
+ import { NoDataDisplay } from '../components/no-data-display';
7
8
  import { LogitScale } from '../shared/charts/LogitScale';
8
9
  import { singleGraphColorRGBAById } from '../shared/charts/colors';
9
10
  import {
@@ -29,12 +30,18 @@ const PrevalenceOverTimeLineChart = ({
29
30
  confidenceIntervalMethod,
30
31
  yAxisMaxConfig,
31
32
  }: PrevalenceOverTimeLineChartProps) => {
32
- const nonNullDateRangeData = data.map((variantData) => {
33
- return {
34
- content: variantData.content.filter((dataPoint) => dataPoint.dateRange !== null),
35
- displayName: variantData.displayName,
36
- };
37
- });
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
+ });
41
+
42
+ if (nonNullDateRangeData.length === 0) {
43
+ return <NoDataDisplay />;
44
+ }
38
45
 
39
46
  const datasets = nonNullDateRangeData
40
47
  .map((graphData, index) => getDataset(graphData, index, confidenceIntervalMethod))
@@ -1,11 +1,15 @@
1
+ import type { StoryObj } from '@storybook/preact';
2
+ import { expect, waitFor } from '@storybook/test';
3
+
4
+ import { LapisUrlContext } from '../LapisUrlContext';
1
5
  import denominatorFilter from './__mockData__/denominatorFilter.json';
2
6
  import denominatorOneDataset from './__mockData__/denominatorFilterOneDataset.json';
3
7
  import numeratorFilterEG from './__mockData__/numeratorFilterEG.json';
4
8
  import numeratorFilterJN1 from './__mockData__/numeratorFilterJN1.json';
9
+ import numeratorFilterNoData from './__mockData__/numeratorFilterNoData.json';
5
10
  import numeratorOneDataset from './__mockData__/numeratorFilterOneDataset.json';
6
11
  import { PrevalenceOverTime, type PrevalenceOverTimeProps } from './prevalence-over-time';
7
12
  import { AGGREGATED_ENDPOINT, LAPIS_URL } from '../../constants';
8
- import { LapisUrlContext } from '../LapisUrlContext';
9
13
 
10
14
  export default {
11
15
  title: 'Visualization/PrevalenceOverTime',
@@ -58,7 +62,7 @@ const Template = {
58
62
  ),
59
63
  };
60
64
 
61
- export const TwoVariants = {
65
+ export const TwoVariants: StoryObj<PrevalenceOverTimeProps> = {
62
66
  ...Template,
63
67
  args: {
64
68
  numeratorFilter: [
@@ -74,10 +78,8 @@ export const TwoVariants = {
74
78
  height: '700px',
75
79
  lapisDateField: 'date',
76
80
  pageSize: 10,
77
- yAxisMaxConfig: {
78
- linear: 1,
79
- logarithmic: 1,
80
- },
81
+ yAxisMaxLinear: 1,
82
+ yAxisMaxLogarithmic: 1,
81
83
  },
82
84
  parameters: {
83
85
  fetchMock: {
@@ -134,7 +136,7 @@ export const TwoVariants = {
134
136
  },
135
137
  };
136
138
 
137
- export const OneVariant = {
139
+ export const OneVariant: StoryObj<PrevalenceOverTimeProps> = {
138
140
  ...Template,
139
141
  args: {
140
142
  numeratorFilter: {
@@ -150,10 +152,8 @@ export const OneVariant = {
150
152
  height: '700px',
151
153
  lapisDateField: 'date',
152
154
  pageSize: 10,
153
- yAxisMaxConfig: {
154
- linear: 1,
155
- logarithmic: 1,
156
- },
155
+ yAxisMaxLinear: 1,
156
+ yAxisMaxLogarithmic: 1,
157
157
  },
158
158
  parameters: {
159
159
  fetchMock: {
@@ -193,3 +193,66 @@ export const OneVariant = {
193
193
  },
194
194
  },
195
195
  };
196
+
197
+ export const ShowsNoDataBanner: StoryObj<PrevalenceOverTimeProps> = {
198
+ ...Template,
199
+ args: {
200
+ numeratorFilter: {
201
+ displayName: 'EG',
202
+ lapisFilter: { country: 'USA', pangoLineage: 'BA.2.86*', dateFrom: '2023-10-01' },
203
+ },
204
+ denominatorFilter: { country: 'USA', dateFrom: '2023-10-01' },
205
+ granularity: 'day',
206
+ smoothingWindow: 7,
207
+ views: ['bar', 'line', 'bubble', 'table'],
208
+ confidenceIntervalMethods: ['wilson'],
209
+ width: '100%',
210
+ height: '700px',
211
+ lapisDateField: 'date',
212
+ pageSize: 10,
213
+ yAxisMaxLinear: 1,
214
+ yAxisMaxLogarithmic: 1,
215
+ },
216
+ parameters: {
217
+ fetchMock: {
218
+ mocks: [
219
+ {
220
+ matcher: {
221
+ name: 'numeratorOneVariant',
222
+ url: AGGREGATED_ENDPOINT,
223
+ body: {
224
+ country: 'USA',
225
+ pangoLineage: 'BA.2.86*',
226
+ dateFrom: '2023-10-01',
227
+ fields: ['date'],
228
+ },
229
+ },
230
+ response: {
231
+ status: 200,
232
+ body: numeratorFilterNoData,
233
+ },
234
+ },
235
+ {
236
+ matcher: {
237
+ name: 'denominatorOneVariant',
238
+ url: AGGREGATED_ENDPOINT,
239
+ body: {
240
+ country: 'USA',
241
+ dateFrom: '2023-10-01',
242
+ fields: ['date'],
243
+ },
244
+ },
245
+ response: {
246
+ status: 200,
247
+ body: numeratorFilterNoData,
248
+ },
249
+ },
250
+ ],
251
+ },
252
+ },
253
+ play: async ({ canvas }) => {
254
+ await waitFor(() => expect(canvas.getByText('No data available.', { exact: false })).toBeVisible(), {
255
+ timeout: 10000,
256
+ });
257
+ },
258
+ };
@@ -80,7 +80,7 @@ export const PrevalenceOverTimeInner: FunctionComponent<PrevalenceOverTimeProps>
80
80
  return <ErrorDisplay error={error} />;
81
81
  }
82
82
 
83
- if (data === null) {
83
+ if (data === null || data.every((variant) => variant.content.length === 0)) {
84
84
  return <NoDataDisplay />;
85
85
  }
86
86