@genspectrum/dashboard-components 0.6.19 → 0.7.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/custom-elements.json +18 -18
- package/dist/assets/mutationOverTimeWorker-BOCXtKzd.js.map +1 -0
- package/dist/dashboard-components.js +96 -99
- package/dist/dashboard-components.js.map +1 -1
- package/dist/genspectrum-components.d.ts +98 -48
- package/package.json +1 -3
- package/src/index.ts +1 -0
- package/src/preact/dateRangeSelector/computeInitialValues.spec.ts +53 -38
- package/src/preact/dateRangeSelector/computeInitialValues.ts +17 -23
- package/src/preact/dateRangeSelector/date-range-selector.stories.tsx +46 -32
- package/src/preact/dateRangeSelector/date-range-selector.tsx +24 -26
- package/src/preact/dateRangeSelector/dateRangeOption.ts +65 -0
- package/src/preact/dateRangeSelector/selectableOptions.ts +17 -66
- package/src/preact/mutationsOverTime/MutationOverTimeData.ts +20 -0
- package/src/preact/mutationsOverTime/getFilteredMutationsOverTime.spec.ts +2 -3
- package/src/preact/mutationsOverTime/getFilteredMutationsOverTimeData.ts +2 -2
- package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +3 -3
- package/src/preact/mutationsOverTime/mutations-over-time.tsx +7 -18
- package/src/query/queryMutationsOverTime.ts +3 -5
- package/src/utils/map2d.spec.ts +52 -13
- package/src/utils/map2d.ts +3 -4
- package/src/web-components/input/gs-date-range-selector.stories.ts +16 -28
- package/src/web-components/input/gs-date-range-selector.tsx +17 -32
- package/standalone-bundle/dashboard-components.js +11687 -12463
- package/standalone-bundle/dashboard-components.js.map +1 -1
- package/dist/assets/mutationOverTimeWorker-BdzqDqvO.js.map +0 -1
|
@@ -1,24 +1,16 @@
|
|
|
1
1
|
import { type Meta, type StoryObj } from '@storybook/preact';
|
|
2
|
-
import { expect, waitFor, within } from '@storybook/test';
|
|
2
|
+
import { expect, userEvent, waitFor, within } from '@storybook/test';
|
|
3
3
|
import dayjs from 'dayjs/esm';
|
|
4
4
|
|
|
5
5
|
import { DateRangeSelector, type DateRangeSelectorProps } from './date-range-selector';
|
|
6
|
-
import {
|
|
7
|
-
PRESET_VALUE_ALL_TIMES,
|
|
8
|
-
PRESET_VALUE_CUSTOM,
|
|
9
|
-
PRESET_VALUE_LAST_2_MONTHS,
|
|
10
|
-
PRESET_VALUE_LAST_2_WEEKS,
|
|
11
|
-
PRESET_VALUE_LAST_3_MONTHS,
|
|
12
|
-
PRESET_VALUE_LAST_6_MONTHS,
|
|
13
|
-
PRESET_VALUE_LAST_MONTH,
|
|
14
|
-
} from './selectableOptions';
|
|
15
6
|
import { previewHandles } from '../../../.storybook/preview';
|
|
16
7
|
import { LAPIS_URL } from '../../constants';
|
|
17
8
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
9
|
+
import { dateRangeOptionPresets } from './dateRangeOption';
|
|
18
10
|
|
|
19
11
|
const earliestDate = '1970-01-01';
|
|
20
12
|
|
|
21
|
-
const meta: Meta<DateRangeSelectorProps
|
|
13
|
+
const meta: Meta<DateRangeSelectorProps> = {
|
|
22
14
|
title: 'Input/DateRangeSelector',
|
|
23
15
|
component: DateRangeSelector,
|
|
24
16
|
parameters: {
|
|
@@ -32,18 +24,9 @@ const meta: Meta<DateRangeSelectorProps<'CustomDateRange'>> = {
|
|
|
32
24
|
control: {
|
|
33
25
|
type: 'select',
|
|
34
26
|
},
|
|
35
|
-
options: [
|
|
36
|
-
PRESET_VALUE_CUSTOM,
|
|
37
|
-
PRESET_VALUE_ALL_TIMES,
|
|
38
|
-
PRESET_VALUE_LAST_2_WEEKS,
|
|
39
|
-
PRESET_VALUE_LAST_MONTH,
|
|
40
|
-
PRESET_VALUE_LAST_2_MONTHS,
|
|
41
|
-
PRESET_VALUE_LAST_3_MONTHS,
|
|
42
|
-
PRESET_VALUE_LAST_6_MONTHS,
|
|
43
|
-
'CustomDateRange',
|
|
44
|
-
],
|
|
27
|
+
options: [dateRangeOptionPresets.lastMonth.label, dateRangeOptionPresets.allTimes.label, 'CustomDateRange'],
|
|
45
28
|
},
|
|
46
|
-
|
|
29
|
+
dateRangeOptions: {
|
|
47
30
|
control: {
|
|
48
31
|
type: 'object',
|
|
49
32
|
},
|
|
@@ -60,9 +43,17 @@ const meta: Meta<DateRangeSelectorProps<'CustomDateRange'>> = {
|
|
|
60
43
|
},
|
|
61
44
|
},
|
|
62
45
|
args: {
|
|
63
|
-
|
|
46
|
+
dateRangeOptions: [
|
|
47
|
+
dateRangeOptionPresets.lastMonth,
|
|
48
|
+
dateRangeOptionPresets.allTimes,
|
|
49
|
+
{
|
|
50
|
+
label: 'CustomDateRange',
|
|
51
|
+
dateFrom: '2021-01-01',
|
|
52
|
+
dateTo: '2021-12-31',
|
|
53
|
+
},
|
|
54
|
+
],
|
|
64
55
|
earliestDate,
|
|
65
|
-
initialValue:
|
|
56
|
+
initialValue: dateRangeOptionPresets.lastMonth.label,
|
|
66
57
|
dateColumn: 'aDateColumn',
|
|
67
58
|
width: '100%',
|
|
68
59
|
initialDateFrom: '',
|
|
@@ -72,11 +63,11 @@ const meta: Meta<DateRangeSelectorProps<'CustomDateRange'>> = {
|
|
|
72
63
|
|
|
73
64
|
export default meta;
|
|
74
65
|
|
|
75
|
-
export const Primary: StoryObj<DateRangeSelectorProps
|
|
66
|
+
export const Primary: StoryObj<DateRangeSelectorProps> = {
|
|
76
67
|
render: (args) => (
|
|
77
68
|
<LapisUrlContext.Provider value={LAPIS_URL}>
|
|
78
69
|
<DateRangeSelector
|
|
79
|
-
|
|
70
|
+
dateRangeOptions={args.dateRangeOptions}
|
|
80
71
|
earliestDate={args.earliestDate}
|
|
81
72
|
initialValue={args.initialValue}
|
|
82
73
|
initialDateFrom={args.initialDateFrom}
|
|
@@ -88,7 +79,7 @@ export const Primary: StoryObj<DateRangeSelectorProps<'CustomDateRange'>> = {
|
|
|
88
79
|
),
|
|
89
80
|
};
|
|
90
81
|
|
|
91
|
-
export const SetCorrectInitialValues: StoryObj<DateRangeSelectorProps
|
|
82
|
+
export const SetCorrectInitialValues: StoryObj<DateRangeSelectorProps> = {
|
|
92
83
|
...Primary,
|
|
93
84
|
args: {
|
|
94
85
|
...Primary.args,
|
|
@@ -111,7 +102,7 @@ export const SetCorrectInitialValues: StoryObj<DateRangeSelectorProps<'CustomDat
|
|
|
111
102
|
|
|
112
103
|
const initialDateFrom = '2000-01-01';
|
|
113
104
|
|
|
114
|
-
export const SetCorrectInitialDateFrom: StoryObj<DateRangeSelectorProps
|
|
105
|
+
export const SetCorrectInitialDateFrom: StoryObj<DateRangeSelectorProps> = {
|
|
115
106
|
...Primary,
|
|
116
107
|
args: {
|
|
117
108
|
...Primary.args,
|
|
@@ -125,7 +116,7 @@ export const SetCorrectInitialDateFrom: StoryObj<DateRangeSelectorProps<'CustomD
|
|
|
125
116
|
const selectField = () => canvas.getByRole('combobox');
|
|
126
117
|
|
|
127
118
|
await waitFor(() => {
|
|
128
|
-
expect(selectField()).toHaveValue(
|
|
119
|
+
expect(selectField()).toHaveValue('Custom');
|
|
129
120
|
expect(dateFrom()).toHaveValue(initialDateFrom);
|
|
130
121
|
expect(dateTo()).toHaveValue(dayjs().format('YYYY-MM-DD'));
|
|
131
122
|
});
|
|
@@ -134,7 +125,7 @@ export const SetCorrectInitialDateFrom: StoryObj<DateRangeSelectorProps<'CustomD
|
|
|
134
125
|
|
|
135
126
|
const initialDateTo = '2000-01-01';
|
|
136
127
|
|
|
137
|
-
export const SetCorrectInitialDateTo: StoryObj<DateRangeSelectorProps
|
|
128
|
+
export const SetCorrectInitialDateTo: StoryObj<DateRangeSelectorProps> = {
|
|
138
129
|
...Primary,
|
|
139
130
|
args: {
|
|
140
131
|
...Primary.args,
|
|
@@ -148,14 +139,37 @@ export const SetCorrectInitialDateTo: StoryObj<DateRangeSelectorProps<'CustomDat
|
|
|
148
139
|
const selectField = () => canvas.getByRole('combobox');
|
|
149
140
|
|
|
150
141
|
await waitFor(() => {
|
|
151
|
-
expect(selectField()).toHaveValue(
|
|
142
|
+
expect(selectField()).toHaveValue('Custom');
|
|
152
143
|
expect(dateFrom()).toHaveValue(earliestDate);
|
|
153
144
|
expect(dateTo()).toHaveValue(initialDateTo);
|
|
154
145
|
});
|
|
155
146
|
},
|
|
156
147
|
};
|
|
157
148
|
|
|
158
|
-
export const
|
|
149
|
+
export const ChangingDateSetsOptionToCustom: StoryObj<DateRangeSelectorProps> = {
|
|
150
|
+
...Primary,
|
|
151
|
+
play: async ({ canvasElement }) => {
|
|
152
|
+
const canvas = within(canvasElement);
|
|
153
|
+
|
|
154
|
+
const dateFrom = () => canvas.getByPlaceholderText('Date from');
|
|
155
|
+
const dateTo = () => canvas.getByPlaceholderText('Date to');
|
|
156
|
+
const selectField = () => canvas.getByRole('combobox');
|
|
157
|
+
|
|
158
|
+
await waitFor(() => {
|
|
159
|
+
expect(selectField()).toHaveValue('Last month');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
await userEvent.type(dateFrom(), '{backspace>12}');
|
|
163
|
+
await userEvent.type(dateFrom(), '2000-01-01');
|
|
164
|
+
await userEvent.click(dateTo());
|
|
165
|
+
|
|
166
|
+
await waitFor(() => {
|
|
167
|
+
expect(selectField()).toHaveValue('Custom');
|
|
168
|
+
});
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
export const HandlesInvalidInitialDateFrom: StoryObj<DateRangeSelectorProps> = {
|
|
159
173
|
...Primary,
|
|
160
174
|
args: {
|
|
161
175
|
...Primary.args,
|
|
@@ -4,33 +4,28 @@ import { useEffect, useRef, useState } from 'preact/hooks';
|
|
|
4
4
|
|
|
5
5
|
import { computeInitialValues } from './computeInitialValues';
|
|
6
6
|
import { toYYYYMMDD } from './dateConversion';
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
getDatesForSelectorValue,
|
|
10
|
-
getSelectableOptions,
|
|
11
|
-
type PresetOptionValues,
|
|
12
|
-
} from './selectableOptions';
|
|
7
|
+
import { type DateRangeOption } from './dateRangeOption';
|
|
8
|
+
import { getDatesForSelectorValue, getSelectableOptions } from './selectableOptions';
|
|
13
9
|
import { ErrorBoundary } from '../components/error-boundary';
|
|
14
10
|
import { Select } from '../components/select';
|
|
15
11
|
import type { ScaleType } from '../shared/charts/getYAxisScale';
|
|
16
12
|
|
|
17
|
-
|
|
13
|
+
const customOption = 'Custom';
|
|
14
|
+
|
|
15
|
+
export interface DateRangeSelectorProps extends DateRangeSelectorPropsInner {
|
|
18
16
|
width: string;
|
|
19
17
|
}
|
|
20
18
|
|
|
21
|
-
export interface DateRangeSelectorPropsInner
|
|
22
|
-
|
|
19
|
+
export interface DateRangeSelectorPropsInner {
|
|
20
|
+
dateRangeOptions: DateRangeOption[];
|
|
23
21
|
earliestDate: string;
|
|
24
|
-
initialValue:
|
|
22
|
+
initialValue: string | undefined;
|
|
25
23
|
initialDateFrom: string;
|
|
26
24
|
initialDateTo: string;
|
|
27
25
|
dateColumn: string;
|
|
28
26
|
}
|
|
29
27
|
|
|
30
|
-
export const DateRangeSelector =
|
|
31
|
-
width,
|
|
32
|
-
...innerProps
|
|
33
|
-
}: DateRangeSelectorProps<CustomLabel>) => {
|
|
28
|
+
export const DateRangeSelector = ({ width, ...innerProps }: DateRangeSelectorProps) => {
|
|
34
29
|
const size = { width, height: '3rem' };
|
|
35
30
|
|
|
36
31
|
return (
|
|
@@ -42,20 +37,20 @@ export const DateRangeSelector = <CustomLabel extends string>({
|
|
|
42
37
|
);
|
|
43
38
|
};
|
|
44
39
|
|
|
45
|
-
export const DateRangeSelectorInner =
|
|
46
|
-
|
|
40
|
+
export const DateRangeSelectorInner = ({
|
|
41
|
+
dateRangeOptions,
|
|
47
42
|
earliestDate = '1900-01-01',
|
|
48
43
|
initialValue,
|
|
49
44
|
dateColumn,
|
|
50
45
|
initialDateFrom,
|
|
51
46
|
initialDateTo,
|
|
52
|
-
}: DateRangeSelectorPropsInner
|
|
47
|
+
}: DateRangeSelectorPropsInner) => {
|
|
53
48
|
const initialValues = computeInitialValues(
|
|
54
49
|
initialValue,
|
|
55
50
|
initialDateFrom,
|
|
56
51
|
initialDateTo,
|
|
57
52
|
earliestDate,
|
|
58
|
-
|
|
53
|
+
dateRangeOptions,
|
|
59
54
|
);
|
|
60
55
|
|
|
61
56
|
const fromDatePickerRef = useRef<HTMLInputElement>(null);
|
|
@@ -64,7 +59,7 @@ export const DateRangeSelectorInner = <CustomLabel extends string>({
|
|
|
64
59
|
const [dateFromPicker, setDateFromPicker] = useState<flatpickr.Instance | null>(null);
|
|
65
60
|
const [dateToPicker, setDateToPicker] = useState<flatpickr.Instance | null>(null);
|
|
66
61
|
|
|
67
|
-
const [selectedDateRange, setSelectedDateRange] = useState<
|
|
62
|
+
const [selectedDateRange, setSelectedDateRange] = useState<string | undefined>(
|
|
68
63
|
initialValues.initialSelectedDateRange,
|
|
69
64
|
);
|
|
70
65
|
|
|
@@ -104,10 +99,10 @@ export const DateRangeSelectorInner = <CustomLabel extends string>({
|
|
|
104
99
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
105
100
|
}, [fromDatePickerRef, toDatePickerRef]);
|
|
106
101
|
|
|
107
|
-
const onSelectChange = (value:
|
|
102
|
+
const onSelectChange = (value: string) => {
|
|
108
103
|
setSelectedDateRange(value);
|
|
109
104
|
|
|
110
|
-
const dateRange = getDatesForSelectorValue(value,
|
|
105
|
+
const dateRange = getDatesForSelectorValue(value, dateRangeOptions, earliestDate);
|
|
111
106
|
|
|
112
107
|
dateToPicker?.set('minDate', dateRange.dateFrom);
|
|
113
108
|
dateFromPicker?.set('maxDate', dateRange.dateTo);
|
|
@@ -130,7 +125,7 @@ export const DateRangeSelectorInner = <CustomLabel extends string>({
|
|
|
130
125
|
|
|
131
126
|
selectedDates.dateFrom = dateFromPicker?.selectedDates[0] || new Date();
|
|
132
127
|
dateToPicker?.set('minDate', dateFromPicker?.selectedDates[0]);
|
|
133
|
-
setSelectedDateRange(
|
|
128
|
+
setSelectedDateRange(customOption);
|
|
134
129
|
|
|
135
130
|
submit();
|
|
136
131
|
};
|
|
@@ -142,7 +137,7 @@ export const DateRangeSelectorInner = <CustomLabel extends string>({
|
|
|
142
137
|
|
|
143
138
|
selectedDates.dateTo = dateToPicker?.selectedDates[0] || new Date();
|
|
144
139
|
dateFromPicker?.set('maxDate', dateToPicker?.selectedDates[0]);
|
|
145
|
-
setSelectedDateRange(
|
|
140
|
+
setSelectedDateRange(customOption);
|
|
146
141
|
|
|
147
142
|
submit();
|
|
148
143
|
};
|
|
@@ -168,14 +163,17 @@ export const DateRangeSelectorInner = <CustomLabel extends string>({
|
|
|
168
163
|
return (
|
|
169
164
|
<div class='flex flex-wrap' ref={divRef}>
|
|
170
165
|
<Select
|
|
171
|
-
items={
|
|
172
|
-
|
|
166
|
+
items={[
|
|
167
|
+
...getSelectableOptions(dateRangeOptions),
|
|
168
|
+
{ label: customOption, value: customOption, disabled: true },
|
|
169
|
+
]}
|
|
170
|
+
selected={selectedDateRange ?? customOption}
|
|
173
171
|
selectStyle='select-bordered rounded-none flex-grow min-w-[7.5rem]'
|
|
174
172
|
onChange={(event: Event) => {
|
|
175
173
|
event.preventDefault();
|
|
176
174
|
const select = event.target as HTMLSelectElement;
|
|
177
175
|
const value = select.value as ScaleType;
|
|
178
|
-
onSelectChange(value
|
|
176
|
+
onSelectChange(value);
|
|
179
177
|
}}
|
|
180
178
|
/>
|
|
181
179
|
<div className={'flex flex-wrap flex-grow'}>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { toYYYYMMDD } from './dateConversion';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A date range option that can be used in the `gs-date-range-selector` component.
|
|
5
|
+
*/
|
|
6
|
+
export type DateRangeOption = {
|
|
7
|
+
/** The label of the date range option that will be shown to the user */
|
|
8
|
+
label: string;
|
|
9
|
+
/**
|
|
10
|
+
* The start date of the date range in the format `YYYY-MM-DD`.
|
|
11
|
+
* If not set, the date range selector will default to the `earliestDate` property.
|
|
12
|
+
*/
|
|
13
|
+
dateFrom?: string;
|
|
14
|
+
/**
|
|
15
|
+
* The end date of the date range in the format `YYYY-MM-DD`.
|
|
16
|
+
* If not set, the date range selector will default to the current date.
|
|
17
|
+
*/
|
|
18
|
+
dateTo?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const today = new Date();
|
|
22
|
+
|
|
23
|
+
const twoWeeksAgo = new Date();
|
|
24
|
+
twoWeeksAgo.setDate(today.getDate() - 14);
|
|
25
|
+
|
|
26
|
+
const lastMonth = new Date(today);
|
|
27
|
+
lastMonth.setMonth(today.getMonth() - 1);
|
|
28
|
+
|
|
29
|
+
const last2Months = new Date(today);
|
|
30
|
+
last2Months.setMonth(today.getMonth() - 2);
|
|
31
|
+
|
|
32
|
+
const last3Months = new Date(today);
|
|
33
|
+
last3Months.setMonth(today.getMonth() - 3);
|
|
34
|
+
|
|
35
|
+
const last6Months = new Date(today);
|
|
36
|
+
last6Months.setMonth(today.getMonth() - 6);
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Presets for the `gs-date-range-selector` component that can be used as `dateRangeOptions`.
|
|
40
|
+
*/
|
|
41
|
+
export const dateRangeOptionPresets = {
|
|
42
|
+
last2Weeks: {
|
|
43
|
+
label: 'Last 2 weeks',
|
|
44
|
+
dateFrom: toYYYYMMDD(twoWeeksAgo),
|
|
45
|
+
},
|
|
46
|
+
lastMonth: {
|
|
47
|
+
label: 'Last month',
|
|
48
|
+
dateFrom: toYYYYMMDD(lastMonth),
|
|
49
|
+
},
|
|
50
|
+
last2Months: {
|
|
51
|
+
label: 'Last 2 months',
|
|
52
|
+
dateFrom: toYYYYMMDD(last2Months),
|
|
53
|
+
},
|
|
54
|
+
last3Months: {
|
|
55
|
+
label: 'Last 3 months',
|
|
56
|
+
dateFrom: toYYYYMMDD(last3Months),
|
|
57
|
+
},
|
|
58
|
+
last6Months: {
|
|
59
|
+
label: 'Last 6 months',
|
|
60
|
+
dateFrom: toYYYYMMDD(last6Months),
|
|
61
|
+
},
|
|
62
|
+
allTimes: {
|
|
63
|
+
label: 'All times',
|
|
64
|
+
},
|
|
65
|
+
} satisfies Record<string, DateRangeOption>;
|
|
@@ -1,79 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
export const PRESET_VALUE_ALL_TIMES = 'allTimes';
|
|
3
|
-
export const PRESET_VALUE_LAST_2_WEEKS = 'last2Weeks';
|
|
4
|
-
export const PRESET_VALUE_LAST_MONTH = 'lastMonth';
|
|
5
|
-
export const PRESET_VALUE_LAST_2_MONTHS = 'last2Months';
|
|
6
|
-
export const PRESET_VALUE_LAST_3_MONTHS = 'last3Months';
|
|
7
|
-
export const PRESET_VALUE_LAST_6_MONTHS = 'last6Months';
|
|
1
|
+
import { type DateRangeOption } from './dateRangeOption';
|
|
8
2
|
|
|
9
|
-
export const
|
|
10
|
-
|
|
11
|
-
[PRESET_VALUE_ALL_TIMES]: { label: 'All times' },
|
|
12
|
-
[PRESET_VALUE_LAST_2_WEEKS]: { label: 'Last 2 weeks' },
|
|
13
|
-
[PRESET_VALUE_LAST_MONTH]: { label: 'Last month' },
|
|
14
|
-
[PRESET_VALUE_LAST_2_MONTHS]: { label: 'Last 2 months' },
|
|
15
|
-
[PRESET_VALUE_LAST_3_MONTHS]: { label: 'Last 3 months' },
|
|
16
|
-
[PRESET_VALUE_LAST_6_MONTHS]: { label: 'Last 6 months' },
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export type PresetOptionValues = keyof typeof presets;
|
|
20
|
-
|
|
21
|
-
export type CustomSelectOption<CustomLabel extends string> = { label: CustomLabel; dateFrom: string; dateTo: string };
|
|
22
|
-
|
|
23
|
-
export const getSelectableOptions = <Label extends string>(customSelectOptions: CustomSelectOption<Label>[]) => {
|
|
24
|
-
const presetOptions = Object.entries(presets).map(([key, value]) => {
|
|
25
|
-
return { label: value.label, value: key };
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
const customOptions = customSelectOptions.map((customSelectOption) => {
|
|
3
|
+
export const getSelectableOptions = (dateRangeOptions: DateRangeOption[]) => {
|
|
4
|
+
return dateRangeOptions.map((customSelectOption) => {
|
|
29
5
|
return { label: customSelectOption.label, value: customSelectOption.label };
|
|
30
6
|
});
|
|
31
|
-
|
|
32
|
-
return [...presetOptions, ...customOptions];
|
|
33
7
|
};
|
|
34
8
|
|
|
35
|
-
export const getDatesForSelectorValue =
|
|
36
|
-
|
|
37
|
-
|
|
9
|
+
export const getDatesForSelectorValue = (
|
|
10
|
+
initialSelectedDateRange: string | undefined,
|
|
11
|
+
dateRangeOptions: DateRangeOption[],
|
|
38
12
|
earliestDate: string,
|
|
39
13
|
) => {
|
|
40
14
|
const today = new Date();
|
|
15
|
+
const defaultDates = { dateFrom: new Date(earliestDate), dateTo: today };
|
|
41
16
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return { dateFrom: new Date(customSelectOption.dateFrom), dateTo: new Date(customSelectOption.dateTo) };
|
|
17
|
+
if (initialSelectedDateRange === undefined) {
|
|
18
|
+
return defaultDates;
|
|
45
19
|
}
|
|
46
20
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
case PRESET_VALUE_LAST_MONTH: {
|
|
54
|
-
const lastMonth = new Date(today);
|
|
55
|
-
lastMonth.setMonth(today.getMonth() - 1);
|
|
56
|
-
return { dateFrom: lastMonth, dateTo: today };
|
|
57
|
-
}
|
|
58
|
-
case PRESET_VALUE_LAST_2_MONTHS: {
|
|
59
|
-
const twoMonthsAgo = new Date(today);
|
|
60
|
-
twoMonthsAgo.setMonth(today.getMonth() - 2);
|
|
61
|
-
return { dateFrom: twoMonthsAgo, dateTo: today };
|
|
62
|
-
}
|
|
63
|
-
case PRESET_VALUE_LAST_3_MONTHS: {
|
|
64
|
-
const threeMonthsAgo = new Date(today);
|
|
65
|
-
threeMonthsAgo.setMonth(today.getMonth() - 3);
|
|
66
|
-
return { dateFrom: threeMonthsAgo, dateTo: today };
|
|
67
|
-
}
|
|
68
|
-
case PRESET_VALUE_LAST_6_MONTHS: {
|
|
69
|
-
const sixMonthsAgo = new Date(today);
|
|
70
|
-
sixMonthsAgo.setMonth(today.getMonth() - 6);
|
|
71
|
-
return { dateFrom: sixMonthsAgo, dateTo: today };
|
|
72
|
-
}
|
|
73
|
-
case PRESET_VALUE_ALL_TIMES: {
|
|
74
|
-
return { dateFrom: new Date(earliestDate), dateTo: today };
|
|
75
|
-
}
|
|
76
|
-
default:
|
|
77
|
-
return { dateFrom: today, dateTo: today };
|
|
21
|
+
const dateRangeOption = dateRangeOptions.find((option) => option.label === initialSelectedDateRange);
|
|
22
|
+
if (dateRangeOption) {
|
|
23
|
+
return {
|
|
24
|
+
dateFrom: new Date(dateRangeOption.dateFrom ?? earliestDate),
|
|
25
|
+
dateTo: new Date(dateRangeOption.dateTo ?? today),
|
|
26
|
+
};
|
|
78
27
|
}
|
|
28
|
+
|
|
29
|
+
return defaultDates;
|
|
79
30
|
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type MutationOverTimeMutationValue,
|
|
3
|
+
serializeSubstitutionOrDeletion,
|
|
4
|
+
serializeTemporal,
|
|
5
|
+
} from '../../query/queryMutationsOverTime';
|
|
6
|
+
import { type Map2d, Map2dBase, type Map2DContents } from '../../utils/map2d';
|
|
7
|
+
import type { Deletion, Substitution } from '../../utils/mutations';
|
|
8
|
+
import type { Temporal } from '../../utils/temporalClass';
|
|
9
|
+
|
|
10
|
+
export type MutationOverTimeDataMap = Map2d<Substitution | Deletion, Temporal, MutationOverTimeMutationValue>;
|
|
11
|
+
|
|
12
|
+
export class BaseMutationOverTimeDataMap extends Map2dBase<
|
|
13
|
+
Substitution | Deletion,
|
|
14
|
+
Temporal,
|
|
15
|
+
MutationOverTimeMutationValue
|
|
16
|
+
> {
|
|
17
|
+
constructor(initialContent?: Map2DContents<Substitution | Deletion, Temporal, MutationOverTimeMutationValue>) {
|
|
18
|
+
super(serializeSubstitutionOrDeletion, serializeTemporal, initialContent);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
|
|
3
|
+
import { BaseMutationOverTimeDataMap } from './MutationOverTimeData';
|
|
3
4
|
import { getFilteredMutationOverTimeData } from './getFilteredMutationsOverTimeData';
|
|
4
|
-
import { type MutationOverTimeMutationValue } from '../../query/queryMutationsOverTime';
|
|
5
5
|
import { type DeletionEntry, type SubstitutionEntry } from '../../types';
|
|
6
|
-
import { Map2dBase } from '../../utils/map2d';
|
|
7
6
|
import { type Deletion, type Substitution } from '../../utils/mutations';
|
|
8
7
|
import { type TemporalClass } from '../../utils/temporalClass';
|
|
9
8
|
import { yearMonthDay } from '../../utils/temporalTestHelpers';
|
|
@@ -194,7 +193,7 @@ describe('getFilteredMutationOverTimeData', () => {
|
|
|
194
193
|
mutationEntries: (SubstitutionEntry<Substitution> | DeletionEntry<Deletion>)[],
|
|
195
194
|
temporals: TemporalClass[] = [someTemporal, anotherTemporal],
|
|
196
195
|
) {
|
|
197
|
-
const data = new
|
|
196
|
+
const data = new BaseMutationOverTimeDataMap();
|
|
198
197
|
|
|
199
198
|
temporals.forEach((temporal) => {
|
|
200
199
|
mutationEntries.forEach((entry) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type MutationOverTimeDataMap } from './MutationOverTimeData';
|
|
2
2
|
import { type SubstitutionOrDeletionEntry } from '../../types';
|
|
3
3
|
import { Map2dView } from '../../utils/map2d';
|
|
4
4
|
import type { Deletion, Substitution } from '../../utils/mutations';
|
|
@@ -6,7 +6,7 @@ import type { DisplayedMutationType } from '../components/mutation-type-selector
|
|
|
6
6
|
import type { DisplayedSegment } from '../components/segment-selector';
|
|
7
7
|
|
|
8
8
|
export function getFilteredMutationOverTimeData(
|
|
9
|
-
data:
|
|
9
|
+
data: MutationOverTimeDataMap,
|
|
10
10
|
overallMutationData: SubstitutionOrDeletionEntry<Substitution, Deletion>[],
|
|
11
11
|
displayedSegments: DisplayedSegment[],
|
|
12
12
|
displayedMutationTypes: DisplayedMutationType[],
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { Fragment, type FunctionComponent, type RefObject } from 'preact';
|
|
2
2
|
import { useEffect, useRef, useState } from 'preact/hooks';
|
|
3
3
|
|
|
4
|
-
import { type
|
|
4
|
+
import { type MutationOverTimeDataMap } from './MutationOverTimeData';
|
|
5
5
|
import { type MutationOverTimeMutationValue } from '../../query/queryMutationsOverTime';
|
|
6
6
|
import { type Deletion, type Substitution } from '../../utils/mutations';
|
|
7
|
-
import {
|
|
7
|
+
import { type Temporal, type TemporalClass, toTemporalClass, YearMonthDayClass } from '../../utils/temporalClass';
|
|
8
8
|
import { type ColorScale, getColorWithingScale, getTextColorForScale } from '../components/color-scale-selector';
|
|
9
9
|
import Tooltip, { type TooltipPosition } from '../components/tooltip';
|
|
10
10
|
import { formatProportion } from '../shared/table/formatProportion';
|
|
11
11
|
|
|
12
12
|
export interface MutationsOverTimeGridProps {
|
|
13
|
-
data:
|
|
13
|
+
data: MutationOverTimeDataMap;
|
|
14
14
|
colorScale: ColorScale;
|
|
15
15
|
}
|
|
16
16
|
|
|
@@ -3,24 +3,19 @@ import { type Dispatch, type StateUpdater, useContext, useMemo, useState } from
|
|
|
3
3
|
|
|
4
4
|
// @ts-expect-error -- uses subpath imports and vite worker import
|
|
5
5
|
import MutationOverTimeWorker from '#mutationOverTime?worker&inline';
|
|
6
|
+
import { BaseMutationOverTimeDataMap, type MutationOverTimeDataMap } from './MutationOverTimeData';
|
|
6
7
|
import { getFilteredMutationOverTimeData } from './getFilteredMutationsOverTimeData';
|
|
7
8
|
import { type MutationOverTimeWorkerResponse } from './mutationOverTimeWorker';
|
|
8
9
|
import MutationsOverTimeGrid from './mutations-over-time-grid';
|
|
9
|
-
import {
|
|
10
|
-
type MutationOverTimeMutationValue,
|
|
11
|
-
type MutationOverTimeQuery,
|
|
12
|
-
serializeSubstitutionOrDeletion,
|
|
13
|
-
serializeTemporal,
|
|
14
|
-
} from '../../query/queryMutationsOverTime';
|
|
10
|
+
import { type MutationOverTimeQuery } from '../../query/queryMutationsOverTime';
|
|
15
11
|
import {
|
|
16
12
|
type LapisFilter,
|
|
17
13
|
type SequenceType,
|
|
18
14
|
type SubstitutionOrDeletionEntry,
|
|
19
15
|
type TemporalGranularity,
|
|
20
16
|
} from '../../types';
|
|
21
|
-
import { type Map2d, Map2dBase } from '../../utils/map2d';
|
|
22
17
|
import { type Deletion, type Substitution } from '../../utils/mutations';
|
|
23
|
-
import {
|
|
18
|
+
import { toTemporalClass } from '../../utils/temporalClass';
|
|
24
19
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
25
20
|
import { type ColorScale } from '../components/color-scale-selector';
|
|
26
21
|
import { ColorScaleSelectorDropdown } from '../components/color-scale-selector-dropdown';
|
|
@@ -99,11 +94,7 @@ export const MutationsOverTimeInner: FunctionComponent<MutationsOverTimeInnerPro
|
|
|
99
94
|
}
|
|
100
95
|
|
|
101
96
|
const { overallMutationData, mutationOverTimeSerialized } = data;
|
|
102
|
-
const mutationOverTimeData = new
|
|
103
|
-
serializeSubstitutionOrDeletion,
|
|
104
|
-
serializeTemporal,
|
|
105
|
-
mutationOverTimeSerialized,
|
|
106
|
-
);
|
|
97
|
+
const mutationOverTimeData = new BaseMutationOverTimeDataMap(mutationOverTimeSerialized);
|
|
107
98
|
return (
|
|
108
99
|
<MutationsOverTimeTabs
|
|
109
100
|
overallMutationData={overallMutationData}
|
|
@@ -114,10 +105,8 @@ export const MutationsOverTimeInner: FunctionComponent<MutationsOverTimeInnerPro
|
|
|
114
105
|
);
|
|
115
106
|
};
|
|
116
107
|
|
|
117
|
-
export type MutationOverTimeData = Map2d<Substitution | Deletion, Temporal, MutationOverTimeMutationValue>;
|
|
118
|
-
|
|
119
108
|
type MutationOverTimeTabsProps = {
|
|
120
|
-
mutationOverTimeData:
|
|
109
|
+
mutationOverTimeData: BaseMutationOverTimeDataMap;
|
|
121
110
|
sequenceType: SequenceType;
|
|
122
111
|
views: View[];
|
|
123
112
|
overallMutationData: SubstitutionOrDeletionEntry<Substitution, Deletion>[];
|
|
@@ -192,7 +181,7 @@ type ToolbarProps = {
|
|
|
192
181
|
setDisplayedMutationTypes: (types: DisplayedMutationType[]) => void;
|
|
193
182
|
proportionInterval: ProportionInterval;
|
|
194
183
|
setProportionInterval: Dispatch<StateUpdater<ProportionInterval>>;
|
|
195
|
-
filteredData:
|
|
184
|
+
filteredData: MutationOverTimeDataMap;
|
|
196
185
|
colorScale: ColorScale;
|
|
197
186
|
setColorScale: Dispatch<StateUpdater<ColorScale>>;
|
|
198
187
|
};
|
|
@@ -236,7 +225,7 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
|
|
|
236
225
|
);
|
|
237
226
|
};
|
|
238
227
|
|
|
239
|
-
function getDownloadData(filteredData:
|
|
228
|
+
function getDownloadData(filteredData: MutationOverTimeDataMap) {
|
|
240
229
|
const dates = filteredData.getSecondAxisKeys().map((date) => toTemporalClass(date));
|
|
241
230
|
|
|
242
231
|
return filteredData.getFirstAxisKeys().map((mutation) => {
|
|
@@ -6,6 +6,7 @@ import { MapOperator } from '../operator/MapOperator';
|
|
|
6
6
|
import { RenameFieldOperator } from '../operator/RenameFieldOperator';
|
|
7
7
|
import { SortOperator } from '../operator/SortOperator';
|
|
8
8
|
import { UserFacingError } from '../preact/components/error-display';
|
|
9
|
+
import { BaseMutationOverTimeDataMap } from '../preact/mutationsOverTime/MutationOverTimeData';
|
|
9
10
|
import { sortSubstitutionsAndDeletions } from '../preact/shared/sort/sortSubstitutionsAndDeletions';
|
|
10
11
|
import {
|
|
11
12
|
type DeletionEntry,
|
|
@@ -15,7 +16,7 @@ import {
|
|
|
15
16
|
type SubstitutionOrDeletionEntry,
|
|
16
17
|
type TemporalGranularity,
|
|
17
18
|
} from '../types';
|
|
18
|
-
import { type Map2d
|
|
19
|
+
import { type Map2d } from '../utils/map2d';
|
|
19
20
|
import {
|
|
20
21
|
type Deletion,
|
|
21
22
|
type DeletionClass,
|
|
@@ -218,10 +219,7 @@ export function groupByMutation(
|
|
|
218
219
|
data: MutationOverTimeData[],
|
|
219
220
|
overallMutationData: (SubstitutionEntry | DeletionEntry)[],
|
|
220
221
|
) {
|
|
221
|
-
const dataArray = new
|
|
222
|
-
serializeSubstitutionOrDeletion,
|
|
223
|
-
serializeTemporal,
|
|
224
|
-
);
|
|
222
|
+
const dataArray = new BaseMutationOverTimeDataMap();
|
|
225
223
|
|
|
226
224
|
const allDates = data.map((mutationData) => mutationData.date);
|
|
227
225
|
|