@genspectrum/dashboard-components 0.18.0 → 0.18.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/custom-elements.json +7 -7
- package/dist/{LineageFilterChangedEvent-DkvWdq_G.js → LineageFilterChangedEvent-ixHQkq8y.js} +2 -2
- package/dist/{LineageFilterChangedEvent-DkvWdq_G.js.map → LineageFilterChangedEvent-ixHQkq8y.js.map} +1 -1
- package/dist/assets/{mutationOverTimeWorker-CPfQDLe6.js.map → mutationOverTimeWorker-ChQTFL68.js.map} +1 -1
- package/dist/components.d.ts +33 -31
- package/dist/components.js +114 -63
- package/dist/components.js.map +1 -1
- package/dist/util.d.ts +28 -28
- package/dist/util.js +1 -1
- package/package.json +1 -1
- package/src/preact/components/color-scale-selector.tsx +1 -1
- package/src/preact/dateRangeFilter/computeInitialValues.spec.ts +2 -2
- package/src/preact/dateRangeFilter/computeInitialValues.ts +1 -1
- package/src/preact/dateRangeFilter/date-range-filter.stories.tsx +3 -5
- package/src/preact/dateRangeFilter/date-range-filter.tsx +12 -7
- package/src/preact/dateRangeFilter/dateRangeOption.ts +1 -1
- package/src/preact/mutationFilter/mutation-filter.tsx +1 -1
- package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutationsByDay.ts +3773 -2290
- package/src/preact/mutationsOverTime/__mockData__/byWeek.ts +3012 -948
- package/src/preact/mutationsOverTime/__mockData__/defaultMockData.ts +8799 -4406
- package/src/preact/mutationsOverTime/__mockData__/showsMessageWhenTooManyMutations.ts +1836 -0
- package/src/preact/mutationsOverTime/getFilteredMutationsOverTime.spec.ts +3 -1
- package/src/preact/mutationsOverTime/mutations-over-time-grid-tooltip.stories.tsx +108 -0
- package/src/preact/mutationsOverTime/mutations-over-time-grid-tooltip.tsx +93 -0
- package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +12 -48
- package/src/preact/mutationsOverTime/mutations-over-time.tsx +2 -1
- package/src/preact/wastewater/mutationsOverTime/computeWastewaterMutationsOverTimeDataPerLocation.spec.ts +5 -5
- package/src/preact/wastewater/mutationsOverTime/computeWastewaterMutationsOverTimeDataPerLocation.ts +1 -3
- package/src/query/queryMutationsOverTime.spec.ts +28 -26
- package/src/query/queryMutationsOverTime.ts +20 -8
- package/src/web-components/input/gs-date-range-filter.stories.ts +1 -1
- package/src/web-components/input/gs-date-range-filter.tsx +7 -5
- package/standalone-bundle/assets/{mutationOverTimeWorker-CERZSdcA.js.map → mutationOverTimeWorker-jChgWnwp.js.map} +1 -1
- package/standalone-bundle/dashboard-components.js +2189 -2149
- package/standalone-bundle/dashboard-components.js.map +1 -1
|
@@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest';
|
|
|
2
2
|
|
|
3
3
|
import { BaseMutationOverTimeDataMap } from './MutationOverTimeData';
|
|
4
4
|
import { getFilteredMutationOverTimeData } from './getFilteredMutationsOverTimeData';
|
|
5
|
+
import { type MutationOverTimeMutationValue } from '../../query/queryMutationsOverTime';
|
|
5
6
|
import { type DeletionEntry, type SubstitutionEntry } from '../../types';
|
|
6
7
|
import { type Deletion, type Substitution } from '../../utils/mutations';
|
|
7
8
|
import { type TemporalClass } from '../../utils/temporalClass';
|
|
@@ -240,10 +241,11 @@ describe('getFilteredMutationOverTimeData', () => {
|
|
|
240
241
|
const someTemporal = yearMonthDay('2021-01-01');
|
|
241
242
|
const anotherTemporal = yearMonthDay('2021-02-02');
|
|
242
243
|
const someMutationOverTimeValue = {
|
|
244
|
+
type: 'value',
|
|
243
245
|
count: 1,
|
|
244
246
|
proportion: inFilter,
|
|
245
247
|
totalCount: 10,
|
|
246
|
-
};
|
|
248
|
+
} satisfies MutationOverTimeMutationValue;
|
|
247
249
|
|
|
248
250
|
function prepareMutationOverTimeData(
|
|
249
251
|
mutationEntries: (SubstitutionEntry<Substitution> | DeletionEntry<Deletion>)[],
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { type Meta, type StoryObj } from '@storybook/preact';
|
|
2
|
+
import { expect, within } from '@storybook/test';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
MutationsOverTimeGridTooltip,
|
|
6
|
+
type MutationsOverTimeGridTooltipProps,
|
|
7
|
+
} from './mutations-over-time-grid-tooltip';
|
|
8
|
+
|
|
9
|
+
const meta: Meta<MutationsOverTimeGridTooltipProps> = {
|
|
10
|
+
title: 'Component/Mutation over time grid tooltip',
|
|
11
|
+
component: MutationsOverTimeGridTooltip,
|
|
12
|
+
argTypes: {
|
|
13
|
+
mutation: { control: 'object' },
|
|
14
|
+
date: { control: 'object' },
|
|
15
|
+
value: { control: 'object' },
|
|
16
|
+
},
|
|
17
|
+
parameters: {
|
|
18
|
+
fetchMock: {},
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default meta;
|
|
23
|
+
|
|
24
|
+
const Template: StoryObj<MutationsOverTimeGridTooltipProps> = {
|
|
25
|
+
render: (args: MutationsOverTimeGridTooltipProps) => <MutationsOverTimeGridTooltip {...args} />,
|
|
26
|
+
args: {
|
|
27
|
+
mutation: {
|
|
28
|
+
type: 'deletion',
|
|
29
|
+
position: 500,
|
|
30
|
+
code: 'A500-',
|
|
31
|
+
valueAtReference: 'A',
|
|
32
|
+
},
|
|
33
|
+
date: {
|
|
34
|
+
type: 'Year',
|
|
35
|
+
year: 2025,
|
|
36
|
+
dateString: '2025',
|
|
37
|
+
},
|
|
38
|
+
value: null,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const NoValue: StoryObj<MutationsOverTimeGridTooltipProps> = {
|
|
43
|
+
...Template,
|
|
44
|
+
play: async ({ canvasElement }) => {
|
|
45
|
+
const canvas = within(canvasElement);
|
|
46
|
+
|
|
47
|
+
await expect(canvas.getByText('2025', { exact: true })).toBeVisible();
|
|
48
|
+
await expect(canvas.getByText('(2025-01-01 - 2025-12-31)')).toBeVisible();
|
|
49
|
+
await expect(canvas.getByText('A500-')).toBeVisible();
|
|
50
|
+
await expect(canvas.getByText('No data')).toBeVisible();
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export const WithValue: StoryObj<MutationsOverTimeGridTooltipProps> = {
|
|
55
|
+
...Template,
|
|
56
|
+
args: {
|
|
57
|
+
...Template.args,
|
|
58
|
+
value: {
|
|
59
|
+
type: 'value',
|
|
60
|
+
proportion: 0.5,
|
|
61
|
+
count: 100,
|
|
62
|
+
totalCount: 300,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
play: async ({ canvasElement }) => {
|
|
66
|
+
const canvas = within(canvasElement);
|
|
67
|
+
|
|
68
|
+
await expect(canvas.getByText('Proportion: 50.00%')).toBeVisible();
|
|
69
|
+
await expect(canvas.getByText('300 samples are in the timeframe')).toBeVisible();
|
|
70
|
+
await expect(canvas.getByText('200 have coverage, of those 100 have the mutation')).toBeVisible();
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export const WithValueBelowThreshold: StoryObj<MutationsOverTimeGridTooltipProps> = {
|
|
75
|
+
...Template,
|
|
76
|
+
args: {
|
|
77
|
+
...Template.args,
|
|
78
|
+
value: {
|
|
79
|
+
type: 'belowThreshold',
|
|
80
|
+
totalCount: 300,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
play: async ({ canvasElement }) => {
|
|
84
|
+
const canvas = within(canvasElement);
|
|
85
|
+
|
|
86
|
+
await expect(canvas.getByText('Proportion: <0.10%')).toBeVisible();
|
|
87
|
+
await expect(canvas.getByText('300 samples are in the timeframe')).toBeVisible();
|
|
88
|
+
await expect(canvas.getByText('none or less than 0.10% have the mutation')).toBeVisible();
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export const WithWastewaterValue: StoryObj<MutationsOverTimeGridTooltipProps> = {
|
|
93
|
+
...Template,
|
|
94
|
+
args: {
|
|
95
|
+
...Template.args,
|
|
96
|
+
value: {
|
|
97
|
+
type: 'wastewaterValue',
|
|
98
|
+
proportion: 0.5,
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
play: async ({ canvasElement }) => {
|
|
102
|
+
const canvas = within(canvasElement);
|
|
103
|
+
|
|
104
|
+
await expect(canvas.getByText('Proportion: 50.00%')).toBeVisible();
|
|
105
|
+
await expect(canvas.queryByText('samples are in the timeframe')).not.toBeInTheDocument();
|
|
106
|
+
await expect(canvas.queryByText('have coverage')).not.toBeInTheDocument();
|
|
107
|
+
},
|
|
108
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { FunctionComponent } from 'preact';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type MutationOverTimeMutationValue,
|
|
5
|
+
MUTATIONS_OVER_TIME_MIN_PROPORTION,
|
|
6
|
+
} from '../../query/queryMutationsOverTime';
|
|
7
|
+
import type { Deletion, Substitution } from '../../utils/mutations';
|
|
8
|
+
import { type Temporal, type TemporalClass, toTemporalClass, YearMonthDayClass } from '../../utils/temporalClass';
|
|
9
|
+
import { formatProportion } from '../shared/table/formatProportion';
|
|
10
|
+
|
|
11
|
+
export type MutationsOverTimeGridTooltipProps = {
|
|
12
|
+
mutation: Substitution | Deletion;
|
|
13
|
+
date: Temporal;
|
|
14
|
+
value: MutationOverTimeMutationValue;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const MutationsOverTimeGridTooltip: FunctionComponent<MutationsOverTimeGridTooltipProps> = ({
|
|
18
|
+
mutation,
|
|
19
|
+
date,
|
|
20
|
+
value,
|
|
21
|
+
}: MutationsOverTimeGridTooltipProps) => {
|
|
22
|
+
const dateClass = toTemporalClass(date);
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<div>
|
|
26
|
+
<p>
|
|
27
|
+
<span className='font-bold'>{dateClass.englishName()}</span>
|
|
28
|
+
</p>
|
|
29
|
+
<p>({timeIntervalDisplay(dateClass)})</p>
|
|
30
|
+
<p>{mutation.code}</p>
|
|
31
|
+
<TooltipValueDescription value={value} />
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const TooltipValueDescription: FunctionComponent<{ value: MutationOverTimeMutationValue }> = ({ value }) => {
|
|
37
|
+
if (value === null) {
|
|
38
|
+
return <p>No data</p>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const proportion =
|
|
42
|
+
value.type === 'belowThreshold'
|
|
43
|
+
? `<${formatProportion(MUTATIONS_OVER_TIME_MIN_PROPORTION)}`
|
|
44
|
+
: formatProportion(value.proportion);
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<>
|
|
48
|
+
<p>Proportion: {proportion}</p>
|
|
49
|
+
<TooltipValueCountsDescription value={value} />
|
|
50
|
+
</>
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const TooltipValueCountsDescription: FunctionComponent<{
|
|
55
|
+
value: NonNullable<MutationOverTimeMutationValue>;
|
|
56
|
+
}> = ({ value }) => {
|
|
57
|
+
switch (value.type) {
|
|
58
|
+
case 'wastewaterValue':
|
|
59
|
+
return;
|
|
60
|
+
case 'belowThreshold':
|
|
61
|
+
return (
|
|
62
|
+
<>
|
|
63
|
+
<p>{value.totalCount} samples are in the timeframe</p>
|
|
64
|
+
<p>none or less than {formatProportion(MUTATIONS_OVER_TIME_MIN_PROPORTION)} have the mutation</p>
|
|
65
|
+
</>
|
|
66
|
+
);
|
|
67
|
+
case 'value':
|
|
68
|
+
return (
|
|
69
|
+
<>
|
|
70
|
+
<p>{value.totalCount} samples are in the timeframe</p>
|
|
71
|
+
<p>
|
|
72
|
+
{totalCountWithCoverage(value.count, value.proportion)} have coverage, of those {value.count}{' '}
|
|
73
|
+
have the mutation
|
|
74
|
+
</p>
|
|
75
|
+
</>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
function totalCountWithCoverage(count: number, proportion: number) {
|
|
81
|
+
if (count === 0) {
|
|
82
|
+
return 0;
|
|
83
|
+
}
|
|
84
|
+
return Math.round(count / proportion);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const timeIntervalDisplay = (date: TemporalClass) => {
|
|
88
|
+
if (date instanceof YearMonthDayClass) {
|
|
89
|
+
return date.toString();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return `${date.firstDay.toString()} - ${date.lastDay.toString()}`;
|
|
93
|
+
};
|
|
@@ -3,12 +3,13 @@ import { type FunctionComponent } from 'preact';
|
|
|
3
3
|
import { useMemo, useState } from 'preact/hooks';
|
|
4
4
|
|
|
5
5
|
import { type MutationOverTimeDataMap } from './MutationOverTimeData';
|
|
6
|
+
import { MutationsOverTimeGridTooltip } from './mutations-over-time-grid-tooltip';
|
|
6
7
|
import { type MutationOverTimeMutationValue } from '../../query/queryMutationsOverTime';
|
|
7
8
|
import { type SequenceType } from '../../types';
|
|
8
9
|
import { type Deletion, type Substitution } from '../../utils/mutations';
|
|
9
|
-
import { type Temporal
|
|
10
|
+
import { type Temporal } from '../../utils/temporalClass';
|
|
10
11
|
import { AnnotatedMutation } from '../components/annotated-mutation';
|
|
11
|
-
import { type ColorScale,
|
|
12
|
+
import { type ColorScale, getColorWithinScale, getTextColorForScale } from '../components/color-scale-selector';
|
|
12
13
|
import Tooltip, { type TooltipPosition } from '../components/tooltip';
|
|
13
14
|
import { formatProportion } from '../shared/table/formatProportion';
|
|
14
15
|
import { type PageSizes, Pagination } from '../shared/tanstackTable/pagination';
|
|
@@ -178,47 +179,25 @@ const ProportionCell: FunctionComponent<{
|
|
|
178
179
|
tooltipPosition: TooltipPosition;
|
|
179
180
|
colorScale: ColorScale;
|
|
180
181
|
}> = ({ value, mutation, date, tooltipPosition, colorScale }) => {
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
const tooltipContent = (
|
|
184
|
-
<div>
|
|
185
|
-
<p>
|
|
186
|
-
<span className='font-bold'>{dateClass.englishName()}</span>
|
|
187
|
-
</p>
|
|
188
|
-
<p>({timeIntervalDisplay(dateClass)})</p>
|
|
189
|
-
<p>{mutation.code}</p>
|
|
190
|
-
{value === null ? (
|
|
191
|
-
<p>No data</p>
|
|
192
|
-
) : (
|
|
193
|
-
<>
|
|
194
|
-
<p>Proportion: {formatProportion(value.proportion)}</p>
|
|
195
|
-
{value.count !== null && value.totalCount !== null && (
|
|
196
|
-
<>
|
|
197
|
-
<p>
|
|
198
|
-
{value.count} / {totalCountWithCoverage(value.count, value.proportion)} with coverage
|
|
199
|
-
</p>
|
|
200
|
-
<p>{value.totalCount} in timeframe</p>
|
|
201
|
-
</>
|
|
202
|
-
)}
|
|
203
|
-
</>
|
|
204
|
-
)}
|
|
205
|
-
</div>
|
|
206
|
-
);
|
|
182
|
+
const proportion = value?.type === 'belowThreshold' ? 0 : value?.proportion;
|
|
207
183
|
|
|
208
184
|
return (
|
|
209
185
|
<div className={'py-1 w-full h-full'}>
|
|
210
|
-
<Tooltip
|
|
186
|
+
<Tooltip
|
|
187
|
+
content={<MutationsOverTimeGridTooltip mutation={mutation} date={date} value={value} />}
|
|
188
|
+
position={tooltipPosition}
|
|
189
|
+
>
|
|
211
190
|
<div
|
|
212
191
|
style={{
|
|
213
|
-
backgroundColor:
|
|
214
|
-
color: getTextColorForScale(
|
|
192
|
+
backgroundColor: getColorWithinScale(proportion, colorScale),
|
|
193
|
+
color: getTextColorForScale(proportion, colorScale),
|
|
215
194
|
}}
|
|
216
195
|
className={`w-full h-full hover:font-bold text-xs group @container`}
|
|
217
196
|
>
|
|
218
197
|
{value === null ? (
|
|
219
|
-
<span className=
|
|
198
|
+
<span className='invisible'>No data</span>
|
|
220
199
|
) : (
|
|
221
|
-
<span className='invisible @[2rem]:visible'>{formatProportion(
|
|
200
|
+
<span className='invisible @[2rem]:visible'>{formatProportion(proportion ?? 0, 0)}</span>
|
|
222
201
|
)}
|
|
223
202
|
</div>
|
|
224
203
|
</Tooltip>
|
|
@@ -226,19 +205,4 @@ const ProportionCell: FunctionComponent<{
|
|
|
226
205
|
);
|
|
227
206
|
};
|
|
228
207
|
|
|
229
|
-
function totalCountWithCoverage(count: number, proportion: number) {
|
|
230
|
-
if (count === 0) {
|
|
231
|
-
return 0;
|
|
232
|
-
}
|
|
233
|
-
return Math.round(count / proportion);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const timeIntervalDisplay = (date: TemporalClass) => {
|
|
237
|
-
if (date instanceof YearMonthDayClass) {
|
|
238
|
-
return date.toString();
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
return `${date.firstDay.toString()} - ${date.lastDay.toString()}`;
|
|
242
|
-
};
|
|
243
|
-
|
|
244
208
|
export default MutationsOverTimeGrid;
|
|
@@ -283,7 +283,8 @@ function getDownloadData(filteredData: MutationOverTimeDataMap) {
|
|
|
283
283
|
return filteredData.getFirstAxisKeys().map((mutation) => {
|
|
284
284
|
return dates.reduce(
|
|
285
285
|
(accumulated, date) => {
|
|
286
|
-
const
|
|
286
|
+
const value = filteredData.get(mutation, date);
|
|
287
|
+
const proportion = value?.type === 'value' || value?.type === 'wastewaterValue' ? value.proportion : '';
|
|
287
288
|
return {
|
|
288
289
|
...accumulated,
|
|
289
290
|
[date.dateString]: proportion,
|
|
@@ -59,10 +59,10 @@ describe('groupMutationDataByLocation', () => {
|
|
|
59
59
|
temporalCache.getYearMonthDay('2025-01-02'),
|
|
60
60
|
]);
|
|
61
61
|
expect(location1Data.getAsArray()).to.deep.equal([
|
|
62
|
-
[{
|
|
62
|
+
[{ type: 'wastewaterValue', proportion: 0.1 }, null],
|
|
63
63
|
[
|
|
64
|
-
{
|
|
65
|
-
{
|
|
64
|
+
{ type: 'wastewaterValue', proportion: 0.2 },
|
|
65
|
+
{ type: 'wastewaterValue', proportion: 0.3 },
|
|
66
66
|
],
|
|
67
67
|
]);
|
|
68
68
|
});
|
|
@@ -96,8 +96,8 @@ describe('groupMutationDataByLocation', () => {
|
|
|
96
96
|
expect(location1Data.getFirstAxisKeys()).to.deep.equal([mutation2, mutation3]);
|
|
97
97
|
expect(location1Data.getSecondAxisKeys()).to.deep.equal([temporalCache.getYearMonthDay('2025-01-01')]);
|
|
98
98
|
expect(location1Data.getAsArray()).to.deep.equal([
|
|
99
|
-
[{
|
|
100
|
-
[{
|
|
99
|
+
[{ type: 'wastewaterValue', proportion: 0.2 }],
|
|
100
|
+
[{ type: 'wastewaterValue', proportion: 0.3 }],
|
|
101
101
|
]);
|
|
102
102
|
});
|
|
103
103
|
|
package/src/preact/wastewater/mutationsOverTime/computeWastewaterMutationsOverTimeDataPerLocation.ts
CHANGED
|
@@ -33,9 +33,7 @@ export function groupMutationDataByLocation(data: WastewaterData, sequenceType:
|
|
|
33
33
|
map.set(
|
|
34
34
|
mutation.mutation,
|
|
35
35
|
row.date,
|
|
36
|
-
mutation.proportion !== null
|
|
37
|
-
? { proportion: mutation.proportion, count: null, totalCount: null }
|
|
38
|
-
: null,
|
|
36
|
+
mutation.proportion !== null ? { type: 'wastewaterValue', proportion: mutation.proportion } : null,
|
|
39
37
|
);
|
|
40
38
|
}
|
|
41
39
|
}
|
|
@@ -101,14 +101,14 @@ describe('queryMutationsOverTime', () => {
|
|
|
101
101
|
|
|
102
102
|
expect(mutationOverTimeData.getAsArray()).to.deep.equal([
|
|
103
103
|
[
|
|
104
|
-
{ proportion: 0.4, count: 4, totalCount: 11 },
|
|
105
|
-
{
|
|
106
|
-
{
|
|
104
|
+
{ type: 'value', proportion: 0.4, count: 4, totalCount: 11 },
|
|
105
|
+
{ type: 'belowThreshold', totalCount: 12 },
|
|
106
|
+
{ type: 'belowThreshold', totalCount: 13 },
|
|
107
107
|
],
|
|
108
108
|
[
|
|
109
|
-
{ proportion: 0.1, count: 1, totalCount: 11 },
|
|
110
|
-
{ proportion: 0.2, count: 2, totalCount: 12 },
|
|
111
|
-
{ proportion: 0.3, count: 3, totalCount: 13 },
|
|
109
|
+
{ type: 'value', proportion: 0.1, count: 1, totalCount: 11 },
|
|
110
|
+
{ type: 'value', proportion: 0.2, count: 2, totalCount: 12 },
|
|
111
|
+
{ type: 'value', proportion: 0.3, count: 3, totalCount: 13 },
|
|
112
112
|
],
|
|
113
113
|
]);
|
|
114
114
|
|
|
@@ -251,8 +251,16 @@ describe('queryMutationsOverTime', () => {
|
|
|
251
251
|
});
|
|
252
252
|
|
|
253
253
|
expect(mutationOverTimeData.getAsArray()).to.deep.equal([
|
|
254
|
-
[
|
|
255
|
-
|
|
254
|
+
[
|
|
255
|
+
{ type: 'value', proportion: 0.4, count: 4, totalCount: 11 },
|
|
256
|
+
null,
|
|
257
|
+
{ type: 'belowThreshold', totalCount: 13 },
|
|
258
|
+
],
|
|
259
|
+
[
|
|
260
|
+
{ type: 'value', proportion: 0.1, count: 1, totalCount: 11 },
|
|
261
|
+
null,
|
|
262
|
+
{ type: 'value', proportion: 0.3, count: 3, totalCount: 13 },
|
|
263
|
+
],
|
|
256
264
|
]);
|
|
257
265
|
|
|
258
266
|
const sequences = mutationOverTimeData.getFirstAxisKeys();
|
|
@@ -444,8 +452,8 @@ describe('queryMutationsOverTime', () => {
|
|
|
444
452
|
|
|
445
453
|
expect(mutationOverTimeData.getAsArray()).to.deep.equal([
|
|
446
454
|
[
|
|
447
|
-
{ proportion: 0.2, count: 2, totalCount: 11 },
|
|
448
|
-
{ proportion: 0.3, count: 3, totalCount: 12 },
|
|
455
|
+
{ type: 'value', proportion: 0.2, count: 2, totalCount: 11 },
|
|
456
|
+
{ type: 'value', proportion: 0.3, count: 3, totalCount: 12 },
|
|
449
457
|
],
|
|
450
458
|
]);
|
|
451
459
|
|
|
@@ -536,8 +544,8 @@ describe('queryMutationsOverTime', () => {
|
|
|
536
544
|
|
|
537
545
|
expect(mutationOverTimeData.getAsArray()).to.deep.equal([
|
|
538
546
|
[
|
|
539
|
-
{ proportion: 0.1, count: 1, totalCount: 11 },
|
|
540
|
-
{ proportion: 0.2, count: 2, totalCount: 12 },
|
|
547
|
+
{ type: 'value', proportion: 0.1, count: 1, totalCount: 11 },
|
|
548
|
+
{ type: 'value', proportion: 0.2, count: 2, totalCount: 12 },
|
|
541
549
|
],
|
|
542
550
|
]);
|
|
543
551
|
|
|
@@ -598,13 +606,7 @@ describe('queryMutationsOverTime', () => {
|
|
|
598
606
|
});
|
|
599
607
|
|
|
600
608
|
expect(mutationOverTimeData.getAsArray()).to.deep.equal([
|
|
601
|
-
[
|
|
602
|
-
{
|
|
603
|
-
proportion: 0.2,
|
|
604
|
-
count: 2,
|
|
605
|
-
totalCount: 11,
|
|
606
|
-
},
|
|
607
|
-
],
|
|
609
|
+
[{ type: 'value', proportion: 0.2, count: 2, totalCount: 11 }],
|
|
608
610
|
]);
|
|
609
611
|
|
|
610
612
|
const sequences = mutationOverTimeData.getFirstAxisKeys();
|
|
@@ -693,12 +695,12 @@ describe('queryMutationsOverTime', () => {
|
|
|
693
695
|
|
|
694
696
|
expect(mutationOverTimeData.getAsArray()).to.deep.equal([
|
|
695
697
|
[
|
|
696
|
-
{ proportion: 0.4, count: 4, totalCount: 11 },
|
|
697
|
-
{
|
|
698
|
+
{ type: 'value', proportion: 0.4, count: 4, totalCount: 11 },
|
|
699
|
+
{ type: 'belowThreshold', totalCount: 12 },
|
|
698
700
|
],
|
|
699
701
|
[
|
|
700
|
-
{ proportion: 0.1, count: 1, totalCount: 11 },
|
|
701
|
-
{ proportion: 0.2, count: 2, totalCount: 12 },
|
|
702
|
+
{ type: 'value', proportion: 0.1, count: 1, totalCount: 11 },
|
|
703
|
+
{ type: 'value', proportion: 0.2, count: 2, totalCount: 12 },
|
|
702
704
|
],
|
|
703
705
|
]);
|
|
704
706
|
|
|
@@ -741,7 +743,7 @@ describe('queryMutationsOverTime', () => {
|
|
|
741
743
|
expect(dates.length).toBe(0);
|
|
742
744
|
});
|
|
743
745
|
|
|
744
|
-
it('should fill with
|
|
746
|
+
it('should fill with "belowThreshold" if the mutation does not exist in a date range but count > 0', async () => {
|
|
745
747
|
const lapisFilter = { field1: 'value1', field2: 'value2' };
|
|
746
748
|
const dateField = 'dateField';
|
|
747
749
|
|
|
@@ -820,8 +822,8 @@ describe('queryMutationsOverTime', () => {
|
|
|
820
822
|
|
|
821
823
|
expect(mutationOverTimeData.getAsArray()).to.deep.equal([
|
|
822
824
|
[
|
|
823
|
-
{ proportion: 0.1, count: 1, totalCount: 11 },
|
|
824
|
-
{
|
|
825
|
+
{ type: 'value', proportion: 0.1, count: 1, totalCount: 11 },
|
|
826
|
+
{ type: 'belowThreshold', totalCount: 11 },
|
|
825
827
|
],
|
|
826
828
|
]);
|
|
827
829
|
});
|
|
@@ -34,13 +34,25 @@ export type MutationOverTimeData = {
|
|
|
34
34
|
totalCount: number;
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
-
export type MutationOverTimeMutationValue =
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
export type MutationOverTimeMutationValue =
|
|
38
|
+
| {
|
|
39
|
+
type: 'value';
|
|
40
|
+
proportion: number;
|
|
41
|
+
count: number;
|
|
42
|
+
totalCount: number;
|
|
43
|
+
}
|
|
44
|
+
| {
|
|
45
|
+
type: 'wastewaterValue';
|
|
46
|
+
proportion: number;
|
|
47
|
+
}
|
|
48
|
+
| {
|
|
49
|
+
type: 'belowThreshold';
|
|
50
|
+
totalCount: number | null;
|
|
51
|
+
}
|
|
52
|
+
| null;
|
|
42
53
|
|
|
43
54
|
const MAX_NUMBER_OF_GRID_COLUMNS = 200;
|
|
55
|
+
export const MUTATIONS_OVER_TIME_MIN_PROPORTION = 0.001;
|
|
44
56
|
|
|
45
57
|
export async function queryOverallMutationData({
|
|
46
58
|
lapisFilter,
|
|
@@ -204,7 +216,7 @@ function fetchAndPrepareDates<LapisDateField extends string>(
|
|
|
204
216
|
}
|
|
205
217
|
|
|
206
218
|
function fetchAndPrepareSubstitutionsOrDeletions(filter: LapisFilter, sequenceType: SequenceType) {
|
|
207
|
-
return new FetchSubstitutionsOrDeletionsOperator(filter, sequenceType,
|
|
219
|
+
return new FetchSubstitutionsOrDeletionsOperator(filter, sequenceType, MUTATIONS_OVER_TIME_MIN_PROPORTION);
|
|
208
220
|
}
|
|
209
221
|
|
|
210
222
|
export function serializeSubstitutionOrDeletion(mutation: Substitution | Deletion) {
|
|
@@ -248,6 +260,7 @@ export function groupByMutation(
|
|
|
248
260
|
|
|
249
261
|
if (dataArray.get(mutation, date) !== undefined) {
|
|
250
262
|
dataArray.set(mutation, date, {
|
|
263
|
+
type: 'value',
|
|
251
264
|
count: mutationEntry.count,
|
|
252
265
|
proportion: mutationEntry.proportion,
|
|
253
266
|
totalCount: mutationData.totalCount,
|
|
@@ -258,8 +271,7 @@ export function groupByMutation(
|
|
|
258
271
|
for (const firstAxisKey of dataArray.getFirstAxisKeys()) {
|
|
259
272
|
if (dataArray.get(firstAxisKey, date) === null) {
|
|
260
273
|
dataArray.set(firstAxisKey, date, {
|
|
261
|
-
|
|
262
|
-
proportion: 0,
|
|
274
|
+
type: 'belowThreshold',
|
|
263
275
|
totalCount: mutationData.totalCount,
|
|
264
276
|
});
|
|
265
277
|
}
|
|
@@ -98,7 +98,7 @@ export const TestRenderAttributesInHtmlInsteadOfUsingPropertyExpression: StoryOb
|
|
|
98
98
|
<gs-date-range-filter
|
|
99
99
|
.dateRangeOptions=${args.dateRangeOptions}
|
|
100
100
|
earliestDate="${args.earliestDate}"
|
|
101
|
-
value="${args.value}"
|
|
101
|
+
value="${args.value ?? 'null'}"
|
|
102
102
|
width="${args.width}"
|
|
103
103
|
lapisDateField="${args.lapisDateField}"
|
|
104
104
|
placeholder="${args.placeholder}"
|
|
@@ -38,12 +38,14 @@ import { PreactLitAdapter } from '../PreactLitAdapter';
|
|
|
38
38
|
* Use this event, when you want to use the filter directly as a LAPIS filter.
|
|
39
39
|
*
|
|
40
40
|
*
|
|
41
|
-
* @fires {CustomEvent<
|
|
41
|
+
* @fires {CustomEvent<string | {dateFrom: string, dateTo: string} | null>} gs-date-range-option-changed
|
|
42
42
|
* Fired when:
|
|
43
43
|
* - The select field is changed,
|
|
44
44
|
* - A date is selected in either of the date pickers,
|
|
45
|
-
* - A date was typed into either of the date input fields, and the input field loses focus ("on blur")
|
|
46
|
-
*
|
|
45
|
+
* - A date was typed into either of the date input fields, and the input field loses focus ("on blur"),
|
|
46
|
+
* - The user deletes the current value by clicking the 'x' button.
|
|
47
|
+
* Contains the selected dateRangeOption or when users select custom values it contains the selected dates
|
|
48
|
+
* or `null` when the input was deleted.
|
|
47
49
|
*
|
|
48
50
|
* Use this event, when you want to control this component in your JS application.
|
|
49
51
|
* You can supply the `detail` of this event to the `value` attribute of this component.
|
|
@@ -80,7 +82,7 @@ export class DateRangeFilterComponent extends PreactLitAdapter {
|
|
|
80
82
|
@property({
|
|
81
83
|
converter: (value) => {
|
|
82
84
|
if (value === null) {
|
|
83
|
-
return
|
|
85
|
+
return null;
|
|
84
86
|
}
|
|
85
87
|
try {
|
|
86
88
|
const result = JSON.parse(value) as unknown;
|
|
@@ -93,7 +95,7 @@ export class DateRangeFilterComponent extends PreactLitAdapter {
|
|
|
93
95
|
}
|
|
94
96
|
},
|
|
95
97
|
})
|
|
96
|
-
value: string | { dateFrom?: string; dateTo?: string } |
|
|
98
|
+
value: string | { dateFrom?: string; dateTo?: string } | null = null;
|
|
97
99
|
|
|
98
100
|
/**
|
|
99
101
|
* The width of the component.
|