@genspectrum/dashboard-components 0.6.3 → 0.6.5

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.
@@ -12,8 +12,14 @@ describe('getFilteredMutationOverTimeData', () => {
12
12
  it('should filter by displayed segments', () => {
13
13
  const data = new Map2d<Substitution | Deletion, Temporal, MutationOverTimeMutationValue>();
14
14
 
15
- data.set(new Substitution('someSegment', 'A', 'T', 123), yearMonthDay('2021-01-01'), 1);
16
- data.set(new Substitution('someOtherSegment', 'A', 'T', 123), yearMonthDay('2021-01-01'), 2);
15
+ data.set(new Substitution('someSegment', 'A', 'T', 123), yearMonthDay('2021-01-01'), {
16
+ count: 1,
17
+ proportion: 0.1,
18
+ });
19
+ data.set(new Substitution('someOtherSegment', 'A', 'T', 123), yearMonthDay('2021-01-01'), {
20
+ count: 2,
21
+ proportion: 0.2,
22
+ });
17
23
 
18
24
  filterDisplayedSegments(
19
25
  [
@@ -32,8 +38,14 @@ describe('getFilteredMutationOverTimeData', () => {
32
38
  it('should filter by mutation types', () => {
33
39
  const data = new Map2d<Substitution | Deletion, Temporal, MutationOverTimeMutationValue>();
34
40
 
35
- data.set(new Substitution('someSegment', 'A', 'T', 123), yearMonthDay('2021-01-01'), 1);
36
- data.set(new Deletion('someOtherSegment', 'A', 123), yearMonthDay('2021-01-01'), 2);
41
+ data.set(new Substitution('someSegment', 'A', 'T', 123), yearMonthDay('2021-01-01'), {
42
+ count: 1,
43
+ proportion: 0.1,
44
+ });
45
+ data.set(new Deletion('someOtherSegment', 'A', 123), yearMonthDay('2021-01-01'), {
46
+ count: 2,
47
+ proportion: 0.2,
48
+ });
37
49
 
38
50
  filterMutationTypes(
39
51
  [
@@ -52,8 +64,8 @@ describe('getFilteredMutationOverTimeData', () => {
52
64
  it('should filter by proportion', () => {
53
65
  const data = new Map2d<Substitution | Deletion, Temporal, MutationOverTimeMutationValue>();
54
66
 
55
- const belowFilter = 0.1;
56
- const aboveFilter = 0.99;
67
+ const belowFilter = { count: 1, proportion: 0.1 };
68
+ const aboveFilter = { count: 99, proportion: 0.99 };
57
69
  const proportionInterval = { min: 0.2, max: 0.9 };
58
70
 
59
71
  const someSubstitution = new Substitution('someSegment', 'A', 'T', 123);
@@ -62,15 +74,15 @@ describe('getFilteredMutationOverTimeData', () => {
62
74
 
63
75
  filterProportion(data, proportionInterval);
64
76
 
65
- expect(data.getAsArray(0).length).to.equal(0);
77
+ expect(data.getAsArray({ count: 0, proportion: 0 }).length).to.equal(0);
66
78
  });
67
79
 
68
80
  it('should not filter if one proportion is within the interval', () => {
69
81
  const data = new Map2d<Substitution | Deletion, Temporal, MutationOverTimeMutationValue>();
70
82
 
71
- const belowFilter = 0.1;
72
- const aboveFilter = 0.99;
73
- const inFilter = 0.5;
83
+ const belowFilter = { count: 1, proportion: 0.1 };
84
+ const aboveFilter = { count: 99, proportion: 0.99 };
85
+ const inFilter = { count: 5, proportion: 0.5 };
74
86
  const proportionInterval = { min: 0.2, max: 0.9 };
75
87
 
76
88
  const someSubstitution = new Substitution('someSegment', 'A', 'T', 123);
@@ -80,7 +92,7 @@ describe('getFilteredMutationOverTimeData', () => {
80
92
 
81
93
  filterProportion(data, proportionInterval);
82
94
 
83
- expect(data.getRow(someSubstitution, 0).length).to.equal(3);
95
+ expect(data.getRow(someSubstitution, { count: 0, proportion: 0 }).length).to.equal(3);
84
96
  });
85
97
  });
86
98
  });
@@ -54,8 +54,12 @@ export function filterProportion(
54
54
  },
55
55
  ) {
56
56
  data.getFirstAxisKeys().forEach((mutation) => {
57
- const row = data.getRow(mutation, 0);
58
- if (!row.some((value) => value >= proportionInterval.min && value <= proportionInterval.max)) {
57
+ const row = data.getRow(mutation, { count: 0, proportion: 0 });
58
+ if (
59
+ !row.some(
60
+ (value) => value.proportion >= proportionInterval.min && value.proportion <= proportionInterval.max,
61
+ )
62
+ ) {
59
63
  data.deleteRow(mutation);
60
64
  }
61
65
  });
@@ -5,7 +5,8 @@ import {
5
5
  type MutationOverTimeMutationValue,
6
6
  } from '../../query/queryMutationsOverTime';
7
7
  import { type Deletion, type Substitution } from '../../utils/mutations';
8
- import { compareTemporal, type Temporal } from '../../utils/temporal';
8
+ import { compareTemporal, type Temporal, YearMonthDay } from '../../utils/temporal';
9
+ import Tooltip from '../components/tooltip';
9
10
  import { singleGraphColorRGBByName } from '../shared/charts/colors';
10
11
  import { formatProportion } from '../shared/table/formatProportion';
11
12
 
@@ -13,42 +14,54 @@ export interface MutationsOverTimeGridProps {
13
14
  data: MutationOverTimeDataGroupedByMutation;
14
15
  }
15
16
 
17
+ const MAX_NUMBER_OF_GRID_ROWS = 100;
18
+
16
19
  const MutationsOverTimeGrid: FunctionComponent<MutationsOverTimeGridProps> = ({ data }) => {
17
- const mutations = data.getFirstAxisKeys();
20
+ const allMutations = data.getFirstAxisKeys();
21
+ const shownMutations = allMutations.slice(0, MAX_NUMBER_OF_GRID_ROWS);
22
+
18
23
  const dates = data.getSecondAxisKeys().sort((a, b) => compareTemporal(a, b));
19
24
 
20
25
  return (
21
- <div
22
- style={{
23
- display: 'grid',
24
- gridTemplateRows: `repeat(${mutations.length}, 24px)`,
25
- gridTemplateColumns: `8rem repeat(${dates.length}, minmax(1.5rem, 1fr))`,
26
- }}
27
- >
28
- {mutations.map((mutation, i) => {
29
- return (
30
- <Fragment key={`fragment-${mutation.toString()}`}>
31
- <div
32
- key={`mutation-${mutation.toString()}`}
33
- style={{ gridRowStart: i + 1, gridColumnStart: 1 }}
34
- >
35
- <MutationCell mutation={mutation} />
36
- </div>
37
- {dates.map((date, j) => {
38
- const value = data.get(mutation, date) ?? 0;
39
- return (
40
- <div
41
- style={{ gridRowStart: i + 1, gridColumnStart: j + 2 }}
42
- key={`${mutation.toString()}-${date.toString()}`}
43
- >
44
- <ProportionCell value={value} date={date} mutation={mutation} />
45
- </div>
46
- );
47
- })}
48
- </Fragment>
49
- );
50
- })}
51
- </div>
26
+ <>
27
+ {allMutations.length > MAX_NUMBER_OF_GRID_ROWS && (
28
+ <div className='pl-2'>
29
+ Showing {MAX_NUMBER_OF_GRID_ROWS} of {allMutations.length} mutations. You can narrow the filter to
30
+ reduce the number of mutations.
31
+ </div>
32
+ )}
33
+ <div
34
+ style={{
35
+ display: 'grid',
36
+ gridTemplateRows: `repeat(${shownMutations.length}, 24px)`,
37
+ gridTemplateColumns: `8rem repeat(${dates.length}, minmax(1.5rem, 1fr))`,
38
+ }}
39
+ >
40
+ {shownMutations.map((mutation, i) => {
41
+ return (
42
+ <Fragment key={`fragment-${mutation.toString()}`}>
43
+ <div
44
+ key={`mutation-${mutation.toString()}`}
45
+ style={{ gridRowStart: i + 1, gridColumnStart: 1 }}
46
+ >
47
+ <MutationCell mutation={mutation} />
48
+ </div>
49
+ {dates.map((date, j) => {
50
+ const value = data.get(mutation, date) ?? { proportion: 0, count: 0 };
51
+ return (
52
+ <div
53
+ style={{ gridRowStart: i + 1, gridColumnStart: j + 2 }}
54
+ key={`${mutation.toString()}-${date.toString()}`}
55
+ >
56
+ <ProportionCell value={value} date={date} mutation={mutation} />
57
+ </div>
58
+ );
59
+ })}
60
+ </Fragment>
61
+ );
62
+ })}
63
+ </div>
64
+ </>
52
65
  );
53
66
  };
54
67
 
@@ -56,22 +69,45 @@ const ProportionCell: FunctionComponent<{
56
69
  value: MutationOverTimeMutationValue;
57
70
  date: Temporal;
58
71
  mutation: Substitution | Deletion;
59
- }> = ({ value }) => {
60
- // TODO(#353): Add tooltip with date, mutation and proportion
72
+ }> = ({ value, mutation, date }) => {
73
+ const tooltipContent = (
74
+ <div>
75
+ <p>
76
+ <span className='font-bold'>{date.englishName()}</span> ({timeIntervalDisplay(date)})
77
+ </p>
78
+ <p>{mutation.code}</p>
79
+ <p>Proportion: {formatProportion(value.proportion)}</p>
80
+ <p>Count: {value.count}</p>
81
+ </div>
82
+ );
83
+
61
84
  return (
62
85
  <>
63
86
  <div className={'py-1'}>
64
- <div
65
- style={{ backgroundColor: backgroundColor(value), color: textColor(value) }}
66
- className='text-center hover:font-bold text-xs'
67
- >
68
- {formatProportion(value, 0)}
69
- </div>
87
+ <Tooltip content={tooltipContent}>
88
+ <div
89
+ style={{
90
+ backgroundColor: backgroundColor(value.proportion),
91
+ color: textColor(value.proportion),
92
+ }}
93
+ className='text-center hover:font-bold text-xs'
94
+ >
95
+ {formatProportion(value.proportion, 0)}
96
+ </div>
97
+ </Tooltip>
70
98
  </div>
71
99
  </>
72
100
  );
73
101
  };
74
102
 
103
+ const timeIntervalDisplay = (date: Temporal) => {
104
+ if (date instanceof YearMonthDay) {
105
+ return date.toString();
106
+ }
107
+
108
+ return `${date.firstDay.toString()} - ${date.lastDay.toString()}`;
109
+ };
110
+
75
111
  const backgroundColor = (proportion: number) => {
76
112
  // TODO(#353): Make minAlpha and maxAlpha configurable
77
113
  const minAlpha = 0.0;
@@ -1,6 +1,8 @@
1
1
  import { type Meta, type StoryObj } from '@storybook/preact';
2
+ import { expect, waitFor } from '@storybook/test';
2
3
 
3
4
  import aggregated_date from './__mockData__/aggregated_date.json';
5
+ import aggregated_tooManyMutations from './__mockData__/aggregated_tooManyMutations.json';
4
6
  import nucleotideMutation_01 from './__mockData__/nucleotideMutations_2024_01.json';
5
7
  import nucleotideMutation_02 from './__mockData__/nucleotideMutations_2024_02.json';
6
8
  import nucleotideMutation_03 from './__mockData__/nucleotideMutations_2024_03.json';
@@ -8,6 +10,7 @@ import nucleotideMutation_04 from './__mockData__/nucleotideMutations_2024_04.js
8
10
  import nucleotideMutation_05 from './__mockData__/nucleotideMutations_2024_05.json';
9
11
  import nucleotideMutation_06 from './__mockData__/nucleotideMutations_2024_06.json';
10
12
  import nucleotideMutation_07 from './__mockData__/nucleotideMutations_2024_07.json';
13
+ import nucleotideMutation_tooManyMutations from './__mockData__/nucleotideMutations_tooManyMutations.json';
11
14
  import { MutationsOverTime, type MutationsOverTimeProps } from './mutations-over-time';
12
15
  import { AGGREGATED_ENDPOINT, LAPIS_URL, NUCLEOTIDE_MUTATIONS_ENDPOINT } from '../../constants';
13
16
  import referenceGenome from '../../lapisApi/__mockData__/referenceGenome.json';
@@ -95,7 +98,7 @@ export const Default: StoryObj<MutationsOverTimeProps> = {
95
98
  pangoLineage: 'JN.1*',
96
99
  dateFrom: '2024-01-01',
97
100
  dateTo: '2024-01-31',
98
- minProportion: 0,
101
+ minProportion: 0.001,
99
102
  },
100
103
  },
101
104
  response: {
@@ -111,7 +114,7 @@ export const Default: StoryObj<MutationsOverTimeProps> = {
111
114
  pangoLineage: 'JN.1*',
112
115
  dateFrom: '2024-02-01',
113
116
  dateTo: '2024-02-29',
114
- minProportion: 0,
117
+ minProportion: 0.001,
115
118
  },
116
119
  },
117
120
  response: {
@@ -127,7 +130,7 @@ export const Default: StoryObj<MutationsOverTimeProps> = {
127
130
  pangoLineage: 'JN.1*',
128
131
  dateFrom: '2024-03-01',
129
132
  dateTo: '2024-03-31',
130
- minProportion: 0,
133
+ minProportion: 0.001,
131
134
  },
132
135
  response: {
133
136
  status: 200,
@@ -143,7 +146,7 @@ export const Default: StoryObj<MutationsOverTimeProps> = {
143
146
  pangoLineage: 'JN.1*',
144
147
  dateFrom: '2024-04-01',
145
148
  dateTo: '2024-04-30',
146
- minProportion: 0,
149
+ minProportion: 0.001,
147
150
  },
148
151
  response: {
149
152
  status: 200,
@@ -159,7 +162,7 @@ export const Default: StoryObj<MutationsOverTimeProps> = {
159
162
  pangoLineage: 'JN.1*',
160
163
  dateFrom: '2024-05-01',
161
164
  dateTo: '2024-05-31',
162
- minProportion: 0,
165
+ minProportion: 0.001,
163
166
  },
164
167
  response: {
165
168
  status: 200,
@@ -175,7 +178,7 @@ export const Default: StoryObj<MutationsOverTimeProps> = {
175
178
  pangoLineage: 'JN.1*',
176
179
  dateFrom: '2024-06-01',
177
180
  dateTo: '2024-06-30',
178
- minProportion: 0,
181
+ minProportion: 0.001,
179
182
  },
180
183
  response: {
181
184
  status: 200,
@@ -183,7 +186,6 @@ export const Default: StoryObj<MutationsOverTimeProps> = {
183
186
  },
184
187
  },
185
188
  },
186
-
187
189
  {
188
190
  matcher: {
189
191
  name: 'nucleotideMutations_07',
@@ -192,7 +194,7 @@ export const Default: StoryObj<MutationsOverTimeProps> = {
192
194
  pangoLineage: 'JN.1*',
193
195
  dateFrom: '2024-07-01',
194
196
  dateTo: '2024-07-31',
195
- minProportion: 0,
197
+ minProportion: 0.001,
196
198
  },
197
199
  response: {
198
200
  status: 200,
@@ -204,3 +206,55 @@ export const Default: StoryObj<MutationsOverTimeProps> = {
204
206
  },
205
207
  },
206
208
  };
209
+
210
+ export const ShowsMessageWhenTooManyMutations: StoryObj<MutationsOverTimeProps> = {
211
+ ...Template,
212
+ args: {
213
+ lapisFilter: { dateFrom: '2023-01-01', dateTo: '2023-12-31' },
214
+ sequenceType: 'nucleotide',
215
+ views: ['grid'],
216
+ width: '100%',
217
+ height: '700px',
218
+ granularity: 'year',
219
+ lapisDateField: 'date',
220
+ },
221
+ parameters: {
222
+ fetchMock: {
223
+ mocks: [
224
+ {
225
+ matcher: {
226
+ name: 'aggregated',
227
+ url: AGGREGATED_ENDPOINT,
228
+ body: {
229
+ dateFrom: '2023-01-01',
230
+ dateTo: '2023-12-31',
231
+ fields: ['date'],
232
+ },
233
+ },
234
+ response: {
235
+ status: 200,
236
+ body: aggregated_tooManyMutations,
237
+ },
238
+ },
239
+ {
240
+ matcher: {
241
+ name: 'nucleotideMutations',
242
+ url: NUCLEOTIDE_MUTATIONS_ENDPOINT,
243
+ body: {
244
+ dateFrom: '2023-01-01',
245
+ dateTo: '2023-12-31',
246
+ minProportion: 0.001,
247
+ },
248
+ response: {
249
+ status: 200,
250
+ body: nucleotideMutation_tooManyMutations,
251
+ },
252
+ },
253
+ },
254
+ ],
255
+ },
256
+ },
257
+ play: async ({ canvas }) => {
258
+ await waitFor(() => expect(canvas.getByText('Showing 100 of 137 mutations.', { exact: false })).toBeVisible());
259
+ },
260
+ };
@@ -27,7 +27,7 @@ describe('queryMutationsOverTime', () => {
27
27
  dateFieldTo: '2023-01-01',
28
28
  minProportion: 0.001,
29
29
  },
30
- response: { data: [getSomeTestMutation(0.1), getSomeOtherTestMutation(0.4)] },
30
+ response: { data: [getSomeTestMutation(0.1, 1), getSomeOtherTestMutation(0.4, 4)] },
31
31
  },
32
32
  {
33
33
  body: {
@@ -36,7 +36,7 @@ describe('queryMutationsOverTime', () => {
36
36
  dateFieldTo: '2023-01-02',
37
37
  minProportion: 0.001,
38
38
  },
39
- response: { data: [getSomeTestMutation(0.2)] },
39
+ response: { data: [getSomeTestMutation(0.2, 2)] },
40
40
  },
41
41
  {
42
42
  body: {
@@ -45,7 +45,7 @@ describe('queryMutationsOverTime', () => {
45
45
  dateFieldTo: '2023-01-03',
46
46
  minProportion: 0.001,
47
47
  },
48
- response: { data: [getSomeTestMutation(0.3)] },
48
+ response: { data: [getSomeTestMutation(0.3, 3)] },
49
49
  },
50
50
  ],
51
51
  'nucleotide',
@@ -53,9 +53,17 @@ describe('queryMutationsOverTime', () => {
53
53
 
54
54
  const result = await queryMutationsOverTimeData(lapisFilter, 'nucleotide', DUMMY_LAPIS_URL, dateField, 'day');
55
55
 
56
- expect(result.getAsArray(0)).to.deep.equal([
57
- [0.1, 0.2, 0.3],
58
- [0.4, 0, 0],
56
+ expect(result.getAsArray({ count: 0, proportion: 0 })).to.deep.equal([
57
+ [
58
+ { proportion: 0.1, count: 1 },
59
+ { proportion: 0.2, count: 2 },
60
+ { proportion: 0.3, count: 3 },
61
+ ],
62
+ [
63
+ { proportion: 0.4, count: 4 },
64
+ { proportion: 0, count: 0 },
65
+ { proportion: 0, count: 0 },
66
+ ],
59
67
  ]);
60
68
 
61
69
  const sequences = result.getFirstAxisKeys();
@@ -91,7 +99,7 @@ describe('queryMutationsOverTime', () => {
91
99
  dateFieldTo: '2023-01-01',
92
100
  minProportion: 0.001,
93
101
  },
94
- response: { data: [getSomeTestMutation(0.1), getSomeOtherTestMutation(0.4)] },
102
+ response: { data: [getSomeTestMutation(0.1, 1), getSomeOtherTestMutation(0.4, 4)] },
95
103
  },
96
104
  {
97
105
  body: {
@@ -109,7 +117,7 @@ describe('queryMutationsOverTime', () => {
109
117
  dateFieldTo: '2023-01-03',
110
118
  minProportion: 0.001,
111
119
  },
112
- response: { data: [getSomeTestMutation(0.3)] },
120
+ response: { data: [getSomeTestMutation(0.3, 3)] },
113
121
  },
114
122
  ],
115
123
  'nucleotide',
@@ -117,9 +125,17 @@ describe('queryMutationsOverTime', () => {
117
125
 
118
126
  const result = await queryMutationsOverTimeData(lapisFilter, 'nucleotide', DUMMY_LAPIS_URL, dateField, 'day');
119
127
 
120
- expect(result.getAsArray(0)).to.deep.equal([
121
- [0.1, 0.3, 0],
122
- [0.4, 0, 0],
128
+ expect(result.getAsArray({ count: 0, proportion: 0 })).to.deep.equal([
129
+ [
130
+ { proportion: 0.1, count: 1 },
131
+ { proportion: 0.3, count: 3 },
132
+ { proportion: 0, count: 0 },
133
+ ],
134
+ [
135
+ { proportion: 0.4, count: 4 },
136
+ { proportion: 0, count: 0 },
137
+ { proportion: 0, count: 0 },
138
+ ],
123
139
  ]);
124
140
 
125
141
  const sequences = result.getFirstAxisKeys();
@@ -181,7 +197,7 @@ describe('queryMutationsOverTime', () => {
181
197
 
182
198
  const result = await queryMutationsOverTimeData(lapisFilter, 'nucleotide', DUMMY_LAPIS_URL, dateField, 'day');
183
199
 
184
- expect(result.getAsArray(0)).to.deep.equal([]);
200
+ expect(result.getAsArray({ count: 0, proportion: 0 })).to.deep.equal([]);
185
201
  expect(result.getFirstAxisKeys()).to.deep.equal([]);
186
202
  expect(result.getSecondAxisKeys()).to.deep.equal([]);
187
203
  });
@@ -209,7 +225,7 @@ describe('queryMutationsOverTime', () => {
209
225
  dateFieldTo: '2023-01-02',
210
226
  minProportion: 0.001,
211
227
  },
212
- response: { data: [getSomeTestMutation(0.2)] },
228
+ response: { data: [getSomeTestMutation(0.2, 2)] },
213
229
  },
214
230
  {
215
231
  body: {
@@ -218,7 +234,7 @@ describe('queryMutationsOverTime', () => {
218
234
  dateFieldTo: '2023-01-03',
219
235
  minProportion: 0.001,
220
236
  },
221
- response: { data: [getSomeTestMutation(0.3)] },
237
+ response: { data: [getSomeTestMutation(0.3, 3)] },
222
238
  },
223
239
  ],
224
240
  'nucleotide',
@@ -226,7 +242,12 @@ describe('queryMutationsOverTime', () => {
226
242
 
227
243
  const result = await queryMutationsOverTimeData(lapisFilter, 'nucleotide', DUMMY_LAPIS_URL, dateField, 'day');
228
244
 
229
- expect(result.getAsArray(0)).to.deep.equal([[0.2, 0.3]]);
245
+ expect(result.getAsArray({ count: 0, proportion: 0 })).to.deep.equal([
246
+ [
247
+ { proportion: 0.2, count: 2 },
248
+ { proportion: 0.3, count: 3 },
249
+ ],
250
+ ]);
230
251
 
231
252
  const sequences = result.getFirstAxisKeys();
232
253
  expect(sequences[0].code).toBe('sequenceName:A123T');
@@ -259,7 +280,7 @@ describe('queryMutationsOverTime', () => {
259
280
  dateFieldTo: '2023-01-01',
260
281
  minProportion: 0.001,
261
282
  },
262
- response: { data: [getSomeTestMutation(0.1)] },
283
+ response: { data: [getSomeTestMutation(0.1, 1)] },
263
284
  },
264
285
  {
265
286
  body: {
@@ -268,7 +289,7 @@ describe('queryMutationsOverTime', () => {
268
289
  dateFieldTo: '2023-01-02',
269
290
  minProportion: 0.001,
270
291
  },
271
- response: { data: [getSomeTestMutation(0.2)] },
292
+ response: { data: [getSomeTestMutation(0.2, 2)] },
272
293
  },
273
294
  ],
274
295
  'nucleotide',
@@ -276,7 +297,12 @@ describe('queryMutationsOverTime', () => {
276
297
 
277
298
  const result = await queryMutationsOverTimeData(lapisFilter, 'nucleotide', DUMMY_LAPIS_URL, dateField, 'day');
278
299
 
279
- expect(result.getAsArray(0)).to.deep.equal([[0.1, 0.2]]);
300
+ expect(result.getAsArray({ count: 0, proportion: 0 })).to.deep.equal([
301
+ [
302
+ { proportion: 0.1, count: 1 },
303
+ { proportion: 0.2, count: 2 },
304
+ ],
305
+ ]);
280
306
 
281
307
  const sequences = result.getFirstAxisKeys();
282
308
  expect(sequences[0].code).toBe('sequenceName:A123T');
@@ -309,7 +335,7 @@ describe('queryMutationsOverTime', () => {
309
335
  dateFieldTo: '2023-01-02',
310
336
  minProportion: 0.001,
311
337
  },
312
- response: { data: [getSomeTestMutation(0.2)] },
338
+ response: { data: [getSomeTestMutation(0.2, 2)] },
313
339
  },
314
340
  ],
315
341
  'nucleotide',
@@ -317,7 +343,7 @@ describe('queryMutationsOverTime', () => {
317
343
 
318
344
  const result = await queryMutationsOverTimeData(lapisFilter, 'nucleotide', DUMMY_LAPIS_URL, dateField, 'day');
319
345
 
320
- expect(result.getAsArray(0)).to.deep.equal([[0.2]]);
346
+ expect(result.getAsArray({ count: 0, proportion: 0 })).to.deep.equal([[{ proportion: 0.2, count: 2 }]]);
321
347
 
322
348
  const sequences = result.getFirstAxisKeys();
323
349
  expect(sequences[0].code).toBe('sequenceName:A123T');
@@ -326,11 +352,11 @@ describe('queryMutationsOverTime', () => {
326
352
  expect(dates[0].toString()).toBe('2023-01-02');
327
353
  });
328
354
 
329
- function getSomeTestMutation(proportion: number) {
355
+ function getSomeTestMutation(proportion: number, count: number) {
330
356
  return {
331
357
  mutation: 'sequenceName:A123T',
332
358
  proportion,
333
- count: 1,
359
+ count,
334
360
  sequenceName: 'sequenceName',
335
361
  mutationFrom: 'A',
336
362
  mutationTo: 'T',
@@ -338,11 +364,11 @@ describe('queryMutationsOverTime', () => {
338
364
  };
339
365
  }
340
366
 
341
- function getSomeOtherTestMutation(proportion: number) {
367
+ function getSomeOtherTestMutation(proportion: number, count: number) {
342
368
  return {
343
369
  mutation: 'otherSequenceName:A123T',
344
370
  proportion,
345
- count: 1,
371
+ count,
346
372
  sequenceName: 'otherSequenceName',
347
373
  mutationFrom: 'G',
348
374
  mutationTo: 'C',
@@ -5,6 +5,7 @@ import { GroupByAndSumOperator } from '../operator/GroupByAndSumOperator';
5
5
  import { MapOperator } from '../operator/MapOperator';
6
6
  import { RenameFieldOperator } from '../operator/RenameFieldOperator';
7
7
  import { SortOperator } from '../operator/SortOperator';
8
+ import { UserFacingError } from '../preact/components/error-display';
8
9
  import {
9
10
  type LapisFilter,
10
11
  type SequenceType,
@@ -26,13 +27,15 @@ export type MutationOverTimeData = {
26
27
  mutations: SubstitutionOrDeletionEntry[];
27
28
  };
28
29
 
29
- export type MutationOverTimeMutationValue = number;
30
+ export type MutationOverTimeMutationValue = { proportion: number; count: number };
30
31
  export type MutationOverTimeDataGroupedByMutation = Map2d<
31
32
  Substitution | Deletion,
32
33
  Temporal,
33
34
  MutationOverTimeMutationValue
34
35
  >;
35
36
 
37
+ const MAX_NUMBER_OF_GRID_COLUMNS = 200;
38
+
36
39
  export async function queryMutationsOverTimeData(
37
40
  lapisFilter: LapisFilter,
38
41
  sequenceType: 'nucleotide' | 'amino acid',
@@ -43,6 +46,15 @@ export async function queryMutationsOverTimeData(
43
46
  ) {
44
47
  const allDates = await getDatesInDataset(lapisFilter, lapis, granularity, lapisDateField, signal);
45
48
 
49
+ if (allDates.length > MAX_NUMBER_OF_GRID_COLUMNS) {
50
+ throw new UserFacingError(
51
+ 'Too many dates',
52
+ `The dataset would contain ${allDates.length} date intervals. ` +
53
+ `Please reduce the number to below ${MAX_NUMBER_OF_GRID_COLUMNS} to display the data. ` +
54
+ 'You can achieve this by either narrowing the date range in the provided LAPIS filter or by selecting a larger granularity.',
55
+ );
56
+ }
57
+
46
58
  const subQueries = allDates.map(async (date) => {
47
59
  const dateFrom = date.firstDay.toString();
48
60
  const dateTo = date.lastDay.toString();
@@ -133,14 +145,17 @@ function fetchAndPrepareSubstitutionsOrDeletions(filter: LapisFilter, sequenceTy
133
145
  }
134
146
 
135
147
  export function groupByMutation(data: MutationOverTimeData[]) {
136
- const dataArray = new Map2d<Substitution | Deletion, Temporal, number>(
148
+ const dataArray = new Map2d<Substitution | Deletion, Temporal, MutationOverTimeMutationValue>(
137
149
  (mutation) => mutation.code,
138
150
  (date) => date.toString(),
139
151
  );
140
152
 
141
153
  data.forEach((mutationData) => {
142
154
  mutationData.mutations.forEach((mutationEntry) => {
143
- dataArray.set(mutationEntry.mutation, mutationData.date, mutationEntry.proportion);
155
+ dataArray.set(mutationEntry.mutation, mutationData.date, {
156
+ count: mutationEntry.count,
157
+ proportion: mutationEntry.proportion,
158
+ });
144
159
  });
145
160
  });
146
161
 
@@ -150,14 +165,14 @@ export function groupByMutation(data: MutationOverTimeData[]) {
150
165
  }
151
166
 
152
167
  function addZeroValuesForDatesWithNoMutationData(
153
- dataArray: Map2d<Substitution | Deletion, Temporal, number>,
168
+ dataArray: Map2d<Substitution | Deletion, Temporal, MutationOverTimeMutationValue>,
154
169
  data: MutationOverTimeData[],
155
170
  ) {
156
171
  if (dataArray.getFirstAxisKeys().length !== 0) {
157
172
  const someMutation = dataArray.getFirstAxisKeys()[0];
158
173
  data.forEach((mutationData) => {
159
174
  if (mutationData.mutations.length === 0) {
160
- dataArray.set(someMutation, mutationData.date, 0);
175
+ dataArray.set(someMutation, mutationData.date, { count: 0, proportion: 0 });
161
176
  }
162
177
  });
163
178
  }