@genspectrum/dashboard-components 1.0.1 → 1.2.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 +2 -2
- package/dist/{NumberRangeFilterChangedEvent-B64OQZjX.js → NumberRangeFilterChangedEvent-CQ32Qy8D.js} +2 -2
- package/dist/NumberRangeFilterChangedEvent-CQ32Qy8D.js.map +1 -0
- package/dist/assets/mutationOverTimeWorker-DpW4YOGl.js.map +1 -0
- package/dist/components.d.ts +29 -29
- package/dist/components.js +186 -145
- package/dist/components.js.map +1 -1
- package/dist/util.d.ts +29 -29
- package/dist/util.js +1 -1
- package/package.json +7 -3
- package/src/lapisApi/lapisApi.ts +2 -2
- package/src/operator/DivisionOperator.ts +4 -2
- package/src/operator/FetchDetailsOperator.ts +1 -1
- package/src/operator/RenameFieldOperator.ts +3 -3
- package/src/preact/MutationAnnotationsContext.tsx +15 -7
- package/src/preact/aggregatedData/aggregate.tsx +0 -5
- package/src/preact/components/annotated-mutation.tsx +0 -1
- package/src/preact/components/clearable-select.stories.tsx +1 -1
- package/src/preact/components/confidence-interval-selector.tsx +1 -1
- package/src/preact/components/error-boundary.tsx +1 -5
- package/src/preact/components/error-display.tsx +1 -1
- package/src/preact/components/fullscreen.tsx +2 -5
- package/src/preact/components/info.stories.tsx +1 -1
- package/src/preact/components/min-max-range-slider.tsx +1 -1
- package/src/preact/components/mutations-over-time-mutations-filter.stories.tsx +109 -0
- package/src/preact/components/mutations-over-time-mutations-filter.tsx +139 -0
- package/src/preact/components/proportion-selector.tsx +4 -4
- package/src/preact/components/select.tsx +1 -1
- package/src/preact/components/table.tsx +1 -1
- package/src/preact/components/tabs.tsx +1 -1
- package/src/preact/components/tooltip.stories.tsx +1 -1
- package/src/preact/components/tooltip.tsx +1 -1
- package/src/preact/genomeViewer/CDSPlot.tsx +3 -3
- package/src/preact/genomeViewer/loadGff3.ts +5 -8
- package/src/preact/lineageFilter/lineage-filter.tsx +1 -1
- package/src/preact/locationFilter/location-filter.tsx +4 -4
- package/src/preact/mutationComparison/getMutationComparisonTableData.ts +1 -3
- package/src/preact/mutationComparison/mutation-comparison-venn.tsx +1 -1
- package/src/preact/mutationComparison/mutation-comparison.tsx +0 -5
- package/src/preact/mutationFilter/mutation-filter-info.tsx +2 -2
- package/src/preact/mutationFilter/mutation-filter.tsx +1 -1
- package/src/preact/mutations/getMutationsGridData.ts +2 -6
- package/src/preact/mutations/getMutationsTableData.ts +1 -1
- package/src/preact/mutations/mutations-grid.tsx +1 -1
- package/src/preact/mutations/mutations.tsx +0 -5
- package/src/preact/mutationsOverTime/getFilteredMutationsOverTime.spec.ts +27 -16
- package/src/preact/mutationsOverTime/getFilteredMutationsOverTimeData.ts +45 -11
- package/src/preact/mutationsOverTime/mutations-over-time.tsx +16 -15
- package/src/preact/numberRangeFilter/number-range-filter.tsx +4 -4
- package/src/preact/numberSequencesOverTime/getNumberOfSequencesOverTimeTableData.ts +1 -4
- package/src/preact/numberSequencesOverTime/number-sequences-over-time.tsx +0 -5
- package/src/preact/prevalenceOverTime/prevalence-over-time-bar-chart.tsx +1 -1
- package/src/preact/prevalenceOverTime/prevalence-over-time-bubble-chart.tsx +1 -1
- package/src/preact/prevalenceOverTime/prevalence-over-time-line-chart.tsx +1 -1
- package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +1 -1
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage-chart.tsx +5 -5
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +1 -4
- package/src/preact/sequencesByLocation/loadMapSource.tsx +5 -2
- package/src/preact/shared/aspectRatio/AspectRatio.tsx +1 -1
- package/src/preact/shared/floating-ui/hooks.ts +2 -2
- package/src/preact/shared/sort/sortMutationPositions.ts +2 -2
- package/src/preact/shared/tanstackTable/pagination.tsx +2 -2
- package/src/preact/shared/tanstackTable/tanstackTable.tsx +1 -1
- package/src/preact/statistic/statistics.tsx +0 -5
- package/src/preact/textFilter/fetchStringAutocompleteList.ts +1 -10
- package/src/preact/textFilter/text-filter.tsx +1 -6
- package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.tsx +14 -8
- package/src/preact/webWorkers/useWebWorker.ts +2 -1
- package/src/preact/webWorkers/workerFunction.ts +2 -2
- package/src/query/computeMapLocationData.ts +1 -1
- package/src/query/queryAggregatedDataOverTime.ts +3 -3
- package/src/query/queryMutationsOverTime.spec.ts +9 -9
- package/src/query/queryMutationsOverTime.ts +22 -16
- package/src/query/queryRelativeGrowthAdvantage.ts +5 -9
- package/src/query/queryWastewaterMutationsOverTime.ts +1 -1
- package/src/types.ts +1 -1
- package/src/utils/mutations.ts +10 -10
- package/src/utils/type-utils.ts +1 -1
- package/src/utils/typeAssertions.spec.ts +1 -1
- package/src/web-components/gs-app.spec-d.ts +1 -1
- package/src/web-components/gs-app.stories.ts +1 -1
- package/src/web-components/input/gs-date-range-filter.tsx +2 -2
- package/src/web-components/input/gs-lineage-filter.tsx +2 -2
- package/src/web-components/input/gs-location-filter.tsx +3 -3
- package/src/web-components/input/gs-mutation-filter.tsx +2 -2
- package/src/web-components/input/gs-number-range-filter.spec.ts +1 -1
- package/src/web-components/input/gs-text-filter.tsx +2 -2
- package/src/web-components/visualization/gs-aggregate.tsx +2 -2
- package/src/web-components/visualization/gs-genome-data-viewer.spec-d.ts +1 -1
- package/src/web-components/visualization/gs-mutation-comparison.tsx +2 -2
- package/src/web-components/visualization/gs-mutations.tsx +2 -2
- package/src/web-components/visualization/gs-number-sequences-over-time.tsx +2 -2
- package/src/web-components/visualization/gs-prevalence-over-time.tsx +2 -2
- package/src/web-components/visualization/gs-relative-growth-advantage.tsx +2 -2
- package/src/web-components/visualization/gs-sequences-by-location.tsx +2 -2
- package/src/web-components/visualization/gs-statistics.tsx +2 -2
- package/standalone-bundle/assets/mutationOverTimeWorker-CZVvQBze.js.map +1 -0
- package/standalone-bundle/dashboard-components.js +3989 -3923
- package/standalone-bundle/dashboard-components.js.map +1 -1
- package/dist/NumberRangeFilterChangedEvent-B64OQZjX.js.map +0 -1
- package/dist/assets/mutationOverTimeWorker-DjH04AQB.js.map +0 -1
- package/src/preact/components/mutations-over-time-text-filter.stories.tsx +0 -57
- package/src/preact/components/mutations-over-time-text-filter.tsx +0 -63
- package/standalone-bundle/assets/mutationOverTimeWorker-B6bf3R3j.js.map +0 -1
|
@@ -11,6 +11,11 @@ import type { DisplayedSegment } from '../components/segment-selector';
|
|
|
11
11
|
export const displayMutationsSchema = z.array(z.string()).min(1);
|
|
12
12
|
export type DisplayMutations = z.infer<typeof displayMutationsSchema>;
|
|
13
13
|
|
|
14
|
+
export type MutationFilter = {
|
|
15
|
+
textFilter: string;
|
|
16
|
+
annotationNameFilter: Set<string>;
|
|
17
|
+
};
|
|
18
|
+
|
|
14
19
|
export type GetFilteredMutationOverTimeDataArgs = {
|
|
15
20
|
data: MutationOverTimeDataMap;
|
|
16
21
|
overallMutationData: SubstitutionOrDeletionEntry<Substitution, Deletion>[];
|
|
@@ -18,7 +23,7 @@ export type GetFilteredMutationOverTimeDataArgs = {
|
|
|
18
23
|
displayedMutationTypes: DisplayedMutationType[];
|
|
19
24
|
proportionInterval: { min: number; max: number };
|
|
20
25
|
displayMutations?: DisplayMutations;
|
|
21
|
-
mutationFilterValue:
|
|
26
|
+
mutationFilterValue: MutationFilter;
|
|
22
27
|
sequenceType: SequenceType;
|
|
23
28
|
annotationProvider: ReturnType<typeof useMutationAnnotationsProvider>;
|
|
24
29
|
};
|
|
@@ -72,25 +77,54 @@ export function getFilteredMutationOverTimeData({
|
|
|
72
77
|
export function mutationOrAnnotationDoNotMatchFilter(
|
|
73
78
|
mutation: Mutation,
|
|
74
79
|
sequenceType: SequenceType,
|
|
75
|
-
|
|
80
|
+
mutationFilter: MutationFilter,
|
|
76
81
|
annotationProvider: ReturnType<typeof useMutationAnnotationsProvider>,
|
|
77
82
|
) {
|
|
78
|
-
|
|
79
|
-
|
|
83
|
+
return !(
|
|
84
|
+
mutationOrAnnotationMatchesTextFilter(mutation, sequenceType, mutationFilter.textFilter, annotationProvider) &&
|
|
85
|
+
mutationMatchesAnnotationFilter(mutation, sequenceType, mutationFilter.annotationNameFilter, annotationProvider)
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function mutationOrAnnotationMatchesTextFilter(
|
|
90
|
+
mutation: Mutation,
|
|
91
|
+
sequenceType: SequenceType,
|
|
92
|
+
textFilter: string,
|
|
93
|
+
annotationProvider: ReturnType<typeof useMutationAnnotationsProvider>,
|
|
94
|
+
) {
|
|
95
|
+
if (textFilter === '') {
|
|
96
|
+
return true;
|
|
80
97
|
}
|
|
81
98
|
|
|
82
|
-
if (mutation.code.includes(
|
|
83
|
-
return
|
|
99
|
+
if (mutation.code.includes(textFilter)) {
|
|
100
|
+
return true;
|
|
84
101
|
}
|
|
85
102
|
|
|
86
103
|
const mutationAnnotations = annotationProvider(mutation, sequenceType);
|
|
87
104
|
if (mutationAnnotations === undefined || mutationAnnotations.length === 0) {
|
|
88
|
-
return
|
|
105
|
+
return false;
|
|
89
106
|
}
|
|
90
|
-
return
|
|
107
|
+
return mutationAnnotations.some(
|
|
91
108
|
(annotation) =>
|
|
92
|
-
annotation.description.includes(
|
|
93
|
-
annotation.name.includes(
|
|
94
|
-
annotation.symbol.includes(
|
|
109
|
+
annotation.description.includes(textFilter) ||
|
|
110
|
+
annotation.name.includes(textFilter) ||
|
|
111
|
+
annotation.symbol.includes(textFilter),
|
|
95
112
|
);
|
|
96
113
|
}
|
|
114
|
+
|
|
115
|
+
function mutationMatchesAnnotationFilter(
|
|
116
|
+
mutation: Mutation,
|
|
117
|
+
sequenceType: SequenceType,
|
|
118
|
+
annotationNameFilter: Set<string>,
|
|
119
|
+
annotationProvider: ReturnType<typeof useMutationAnnotationsProvider>,
|
|
120
|
+
) {
|
|
121
|
+
if (annotationNameFilter.size === 0) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const mutationAnnotations = annotationProvider(mutation, sequenceType);
|
|
126
|
+
if (mutationAnnotations === undefined || mutationAnnotations.length === 0) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
return mutationAnnotations.some((annotation) => annotationNameFilter.has(annotation.name));
|
|
130
|
+
}
|
|
@@ -5,10 +5,13 @@ import z from 'zod';
|
|
|
5
5
|
// @ts-expect-error -- uses subpath imports and vite worker import
|
|
6
6
|
import MutationOverTimeWorker from '#mutationOverTime?worker&inline';
|
|
7
7
|
import { BaseMutationOverTimeDataMap, type MutationOverTimeDataMap } from './MutationOverTimeData';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
displayMutationsSchema,
|
|
10
|
+
getFilteredMutationOverTimeData,
|
|
11
|
+
type MutationFilter,
|
|
12
|
+
} from './getFilteredMutationsOverTimeData';
|
|
9
13
|
import { type MutationOverTimeWorkerResponse } from './mutationOverTimeWorker';
|
|
10
14
|
import MutationsOverTimeGrid from './mutations-over-time-grid';
|
|
11
|
-
import { type MutationOverTimeQuery } from '../../query/queryMutationsOverTime';
|
|
12
15
|
import {
|
|
13
16
|
lapisFilterSchema,
|
|
14
17
|
sequenceTypeSchema,
|
|
@@ -29,7 +32,7 @@ import { Fullscreen } from '../components/fullscreen';
|
|
|
29
32
|
import Info, { InfoComponentCode, InfoHeadline1, InfoParagraph } from '../components/info';
|
|
30
33
|
import { LoadingDisplay } from '../components/loading-display';
|
|
31
34
|
import { type DisplayedMutationType, MutationTypeSelector } from '../components/mutation-type-selector';
|
|
32
|
-
import {
|
|
35
|
+
import { MutationsOverTimeMutationsFilter } from '../components/mutations-over-time-mutations-filter';
|
|
33
36
|
import { NoDataDisplay } from '../components/no-data-display';
|
|
34
37
|
import type { ProportionInterval } from '../components/proportion-selector';
|
|
35
38
|
import { ProportionSelectorDropdown } from '../components/proportion-selector-dropdown';
|
|
@@ -87,7 +90,7 @@ export const MutationsOverTimeInner: FunctionComponent<MutationsOverTimeProps> =
|
|
|
87
90
|
};
|
|
88
91
|
}, [granularity, lapis, lapisDateField, lapisFilter, sequenceType]);
|
|
89
92
|
|
|
90
|
-
const { data, error, isLoading } = useWebWorker<
|
|
93
|
+
const { data, error, isLoading } = useWebWorker<MutationOverTimeWorkerResponse>(
|
|
91
94
|
messageToWorker,
|
|
92
95
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
93
96
|
MutationOverTimeWorker,
|
|
@@ -101,7 +104,7 @@ export const MutationsOverTimeInner: FunctionComponent<MutationsOverTimeProps> =
|
|
|
101
104
|
throw error;
|
|
102
105
|
}
|
|
103
106
|
|
|
104
|
-
if (data ===
|
|
107
|
+
if (data === undefined || data.overallMutationData.length === 0) {
|
|
105
108
|
return <NoDataDisplay />;
|
|
106
109
|
}
|
|
107
110
|
|
|
@@ -129,7 +132,10 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
|
|
|
129
132
|
}) => {
|
|
130
133
|
const tabsRef = useDispatchFinishedLoadingEvent();
|
|
131
134
|
|
|
132
|
-
const [mutationFilterValue, setMutationFilterValue] = useState(
|
|
135
|
+
const [mutationFilterValue, setMutationFilterValue] = useState<MutationFilter>({
|
|
136
|
+
textFilter: '',
|
|
137
|
+
annotationNameFilter: new Set(),
|
|
138
|
+
});
|
|
133
139
|
const annotationProvider = useMutationAnnotationsProvider();
|
|
134
140
|
|
|
135
141
|
const [proportionInterval, setProportionInterval] = useState(originalComponentProps.initialMeanProportionInterval);
|
|
@@ -166,13 +172,8 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
|
|
|
166
172
|
]);
|
|
167
173
|
|
|
168
174
|
const getTab = (view: MutationsOverTimeView) => {
|
|
169
|
-
if (filteredData === undefined) {
|
|
170
|
-
return {
|
|
171
|
-
title: 'Calculating',
|
|
172
|
-
content: <LoadingDisplay />,
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
175
|
switch (view) {
|
|
176
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- for extensibility
|
|
176
177
|
case 'grid':
|
|
177
178
|
return {
|
|
178
179
|
title: 'Grid',
|
|
@@ -227,8 +228,8 @@ type ToolbarProps = {
|
|
|
227
228
|
colorScale: ColorScale;
|
|
228
229
|
setColorScale: Dispatch<StateUpdater<ColorScale>>;
|
|
229
230
|
originalComponentProps: MutationsOverTimeProps;
|
|
230
|
-
mutationFilterValue:
|
|
231
|
-
setFilterValue:
|
|
231
|
+
mutationFilterValue: MutationFilter;
|
|
232
|
+
setFilterValue: Dispatch<StateUpdater<MutationFilter>>;
|
|
232
233
|
};
|
|
233
234
|
|
|
234
235
|
const Toolbar: FunctionComponent<ToolbarProps> = ({
|
|
@@ -248,7 +249,7 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
|
|
|
248
249
|
}) => {
|
|
249
250
|
return (
|
|
250
251
|
<>
|
|
251
|
-
<
|
|
252
|
+
<MutationsOverTimeMutationsFilter setFilterValue={setFilterValue} value={mutationFilterValue} />
|
|
252
253
|
{activeTab === 'Grid' && (
|
|
253
254
|
<ColorScaleSelectorDropdown colorScale={colorScale} setColorScale={setColorScale} />
|
|
254
255
|
)}
|
|
@@ -90,10 +90,10 @@ const NumberRangeFilterInner: FunctionComponent<NumberRangeFilterInnerProps> = (
|
|
|
90
90
|
<div className={`join-item w-full flex input px-2 ${inputError}`}>
|
|
91
91
|
<input
|
|
92
92
|
type='text'
|
|
93
|
-
|
|
93
|
+
inputMode='numeric'
|
|
94
94
|
className='w-full grow capitalize'
|
|
95
95
|
placeholder={`${lapisField} from`}
|
|
96
|
-
value={currentRange.inputState.min
|
|
96
|
+
value={currentRange.inputState.min}
|
|
97
97
|
onInput={(e) => {
|
|
98
98
|
dispatchRange({
|
|
99
99
|
type: SetRangeActionType.SET_MIN,
|
|
@@ -116,10 +116,10 @@ const NumberRangeFilterInner: FunctionComponent<NumberRangeFilterInnerProps> = (
|
|
|
116
116
|
<div className={`join-item w-full flex input px-2 ${inputError}`}>
|
|
117
117
|
<input
|
|
118
118
|
type='text'
|
|
119
|
-
|
|
119
|
+
inputMode='numeric'
|
|
120
120
|
className='w-full grow capitalize'
|
|
121
121
|
placeholder={`${lapisField} to`}
|
|
122
|
-
value={currentRange.inputState.max
|
|
122
|
+
value={currentRange.inputState.max}
|
|
123
123
|
onInput={(e) => {
|
|
124
124
|
dispatchRange({
|
|
125
125
|
type: SetRangeActionType.SET_MAX,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { NumberOfSequencesDatasets } from '../../query/queryNumberOfSequencesOverTime';
|
|
2
2
|
import { generateAllInRange, getMinMaxTemporal, type TemporalClass } from '../../utils/temporalClass';
|
|
3
3
|
|
|
4
|
-
type TableRow<DateRangeKey extends string> =
|
|
4
|
+
type TableRow<DateRangeKey extends string> = Record<DateRangeKey, string> & Record<string, number>;
|
|
5
5
|
|
|
6
6
|
export const getNumberOfSequencesOverTimeTableData = <DateRangeKey extends string>(
|
|
7
7
|
data: NumberOfSequencesDatasets,
|
|
@@ -17,9 +17,6 @@ export const getNumberOfSequencesOverTimeTableData = <DateRangeKey extends strin
|
|
|
17
17
|
.reduce((acc, keys) => new Set([...acc, ...keys]), new Set<TemporalClass | null>());
|
|
18
18
|
|
|
19
19
|
const minMax = getMinMaxTemporal(allDateRangesThatOccurInData);
|
|
20
|
-
if (minMax === null) {
|
|
21
|
-
return [];
|
|
22
|
-
}
|
|
23
20
|
|
|
24
21
|
const allDateRanges: (TemporalClass | null)[] = generateAllInRange(minMax.min, minMax.max);
|
|
25
22
|
|
|
@@ -18,7 +18,6 @@ import { ErrorBoundary } from '../components/error-boundary';
|
|
|
18
18
|
import { Fullscreen } from '../components/fullscreen';
|
|
19
19
|
import Info, { InfoComponentCode, InfoHeadline1, InfoParagraph } from '../components/info';
|
|
20
20
|
import { LoadingDisplay } from '../components/loading-display';
|
|
21
|
-
import { NoDataDisplay } from '../components/no-data-display';
|
|
22
21
|
import { ResizeContainer } from '../components/resize-container';
|
|
23
22
|
import { ScalingSelector } from '../components/scaling-selector';
|
|
24
23
|
import Tabs from '../components/tabs';
|
|
@@ -76,10 +75,6 @@ const NumberSequencesOverTimeInner = (componentProps: NumberSequencesOverTimePro
|
|
|
76
75
|
throw error;
|
|
77
76
|
}
|
|
78
77
|
|
|
79
|
-
if (data === null) {
|
|
80
|
-
return <NoDataDisplay />;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
78
|
return <NumberSequencesOverTimeTabs data={data} originalComponentProps={componentProps} />;
|
|
84
79
|
};
|
|
85
80
|
|
|
@@ -56,7 +56,7 @@ const PrevalenceOverTimeBarChart = ({
|
|
|
56
56
|
|
|
57
57
|
const maxY =
|
|
58
58
|
yAxisScaleType !== 'logit'
|
|
59
|
-
? getYAxisMax(maxInData(nullFirstData), yAxisMaxConfig
|
|
59
|
+
? getYAxisMax(maxInData(nullFirstData), yAxisMaxConfig[yAxisScaleType])
|
|
60
60
|
: undefined;
|
|
61
61
|
|
|
62
62
|
return {
|
|
@@ -54,7 +54,7 @@ const PrevalenceOverTimeBubbleChart = ({
|
|
|
54
54
|
|
|
55
55
|
const maxY =
|
|
56
56
|
yAxisScaleType !== 'logit'
|
|
57
|
-
? getYAxisMax(maxInData(nonNullDateRangeData), yAxisMaxConfig
|
|
57
|
+
? getYAxisMax(maxInData(nonNullDateRangeData), yAxisMaxConfig[yAxisScaleType])
|
|
58
58
|
: undefined;
|
|
59
59
|
|
|
60
60
|
return {
|
|
@@ -55,7 +55,7 @@ const PrevalenceOverTimeLineChart = ({
|
|
|
55
55
|
|
|
56
56
|
const maxY =
|
|
57
57
|
yAxisScaleType !== 'logit'
|
|
58
|
-
? getYAxisMax(maxInData(nonNullDateRangeData), yAxisMaxConfig
|
|
58
|
+
? getYAxisMax(maxInData(nonNullDateRangeData), yAxisMaxConfig[yAxisScaleType])
|
|
59
59
|
: undefined;
|
|
60
60
|
|
|
61
61
|
return {
|
|
@@ -90,7 +90,7 @@ export const PrevalenceOverTimeInner: FunctionComponent<PrevalenceOverTimeProps>
|
|
|
90
90
|
throw error;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
if (data
|
|
93
|
+
if (data.every((variant) => variant.content.length === 0)) {
|
|
94
94
|
return <NoDataDisplay />;
|
|
95
95
|
}
|
|
96
96
|
|
|
@@ -43,7 +43,7 @@ const RelativeGrowthAdvantageChart = ({
|
|
|
43
43
|
const config = useMemo<ChartConfiguration>(() => {
|
|
44
44
|
const maxY =
|
|
45
45
|
yAxisScaleType !== 'logit'
|
|
46
|
-
? getYAxisMax(Math.max(...data.proportion), yAxisMaxConfig
|
|
46
|
+
? getYAxisMax(Math.max(...data.proportion), yAxisMaxConfig[yAxisScaleType])
|
|
47
47
|
: undefined;
|
|
48
48
|
|
|
49
49
|
return {
|
|
@@ -92,11 +92,11 @@ const RelativeGrowthAdvantageDisplay = ({
|
|
|
92
92
|
relativeAdvantageUpperBound: number;
|
|
93
93
|
}) => {
|
|
94
94
|
return (
|
|
95
|
-
<div
|
|
96
|
-
<span
|
|
95
|
+
<div className='mx-auto flex items-end flex-wrap'>
|
|
96
|
+
<span className='text-[#606060]'>Relative advantage:</span>
|
|
97
97
|
<div>
|
|
98
|
-
<span
|
|
99
|
-
<span
|
|
98
|
+
<span className='text-2xl ml-3'> {formatProportion(relativeAdvantage)} </span>
|
|
99
|
+
<span className='ml-2.5'>
|
|
100
100
|
({formatProportion(relativeAdvantageLowerBound)} - {formatProportion(relativeAdvantageUpperBound)})
|
|
101
101
|
</span>
|
|
102
102
|
</div>
|
|
@@ -77,10 +77,6 @@ export const RelativeGrowthAdvantageInner: FunctionComponent<RelativeGrowthAdvan
|
|
|
77
77
|
throw error;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
if (data === null) {
|
|
81
|
-
return <NoDataDisplay />;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
80
|
return (
|
|
85
81
|
<RelativeGrowthAdvantageTabs
|
|
86
82
|
data={data}
|
|
@@ -110,6 +106,7 @@ const RelativeGrowthAdvantageTabs: FunctionComponent<RelativeGrowthAdvantageTabs
|
|
|
110
106
|
|
|
111
107
|
const getTab = (view: RelativeGrowthAdvantageView) => {
|
|
112
108
|
switch (view) {
|
|
109
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- for extensibility
|
|
113
110
|
case 'line':
|
|
114
111
|
return {
|
|
115
112
|
title: 'Line',
|
|
@@ -18,6 +18,7 @@ export type GeoJsonFeatureProperties = {
|
|
|
18
18
|
|
|
19
19
|
export async function loadMapSource(mapSource: MapSource) {
|
|
20
20
|
switch (mapSource.type) {
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- for future extensibility
|
|
21
22
|
case 'topojson':
|
|
22
23
|
return await loadTopojsonMap(mapSource);
|
|
23
24
|
}
|
|
@@ -28,17 +29,19 @@ async function loadTopojsonMap(
|
|
|
28
29
|
): Promise<FeatureCollection<GeometryObject, GeoJsonFeatureProperties>> {
|
|
29
30
|
const response = await fetch(mapSource.url);
|
|
30
31
|
const topology = (await response.json()) as Topology;
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- for future extensibility
|
|
31
33
|
if (topology?.type !== 'Topology') {
|
|
32
34
|
throw new UserFacingError(
|
|
33
35
|
'Invalid map source',
|
|
34
36
|
`JSON downloaded from ${mapSource.url} does not look like a topojson Topology definition: missing 'type: "Topology"', got '${JSON.stringify(topology).substring(0, 100)}'`,
|
|
35
37
|
);
|
|
36
38
|
}
|
|
37
|
-
const object = topology
|
|
39
|
+
const object = topology.objects[mapSource.topologyObjectsKey] as GeometryCollection<GeoJsonFeatureProperties>;
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- to be safe
|
|
38
41
|
if (object?.type !== 'GeometryCollection') {
|
|
39
42
|
throw new UserFacingError(
|
|
40
43
|
'Invalid map source',
|
|
41
|
-
`JSON downloaded from ${mapSource.url} does not have a GeometryCollection at key objects.${mapSource.topologyObjectsKey}, got '${JSON.stringify(topology)
|
|
44
|
+
`JSON downloaded from ${mapSource.url} does not have a GeometryCollection at key objects.${mapSource.topologyObjectsKey}, got '${JSON.stringify(topology).substring(0, 100)}'`,
|
|
42
45
|
);
|
|
43
46
|
}
|
|
44
47
|
return topojson.feature(topology, object);
|
|
@@ -6,7 +6,7 @@ export function AspectRatio({ children, aspectRatio }: PropsWithChildren<{ aspec
|
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
return (
|
|
9
|
-
<div
|
|
9
|
+
<div className={`w-full relative`} style={{ paddingTop: `${aspectRatio}%` }}>
|
|
10
10
|
<div className='absolute inset-0'>{children}</div>
|
|
11
11
|
</div>
|
|
12
12
|
);
|
|
@@ -6,10 +6,10 @@ import type { MutableRefObject } from 'react';
|
|
|
6
6
|
export function useFloatingUi(
|
|
7
7
|
referenceRef: MutableRefObject<HTMLElement | null>,
|
|
8
8
|
floatingRef: MutableRefObject<HTMLElement | null>,
|
|
9
|
-
middleware?:
|
|
9
|
+
middleware?: (Middleware | null | undefined | false)[],
|
|
10
10
|
placement?: Placement,
|
|
11
11
|
) {
|
|
12
|
-
const cleanupRef = useRef<() => void | null>(null);
|
|
12
|
+
const cleanupRef = useRef<(() => void) | null>(null);
|
|
13
13
|
|
|
14
14
|
useEffect(() => {
|
|
15
15
|
if (!referenceRef.current || !floatingRef.current) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
const pattern = /(?:([A-Za-z0-9]+):)?(\d+)$/;
|
|
2
2
|
|
|
3
3
|
export const sortMutationPositions = (a: string, b: string) => {
|
|
4
|
-
const aMatch =
|
|
5
|
-
const bMatch =
|
|
4
|
+
const aMatch = pattern.exec(a);
|
|
5
|
+
const bMatch = pattern.exec(b);
|
|
6
6
|
|
|
7
7
|
if (aMatch && bMatch) {
|
|
8
8
|
if (aMatch[1] !== bMatch[1]) {
|
|
@@ -67,10 +67,10 @@ function PageSizeSelector({
|
|
|
67
67
|
className={`select select-ghost select-sm ${heightForSmallerLines}`}
|
|
68
68
|
value={pageSize}
|
|
69
69
|
onChange={(e) => {
|
|
70
|
-
const pageSize = Number(e.currentTarget
|
|
70
|
+
const pageSize = Number(e.currentTarget.value);
|
|
71
71
|
if (Number.isNaN(pageSize)) {
|
|
72
72
|
throw new Error(
|
|
73
|
-
`Invalid page size selected: The value ${e.currentTarget
|
|
73
|
+
`Invalid page size selected: The value ${e.currentTarget.value} could not be parsed as a number.`,
|
|
74
74
|
);
|
|
75
75
|
}
|
|
76
76
|
setPageSize(pageSize);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createTable, type RowData, type TableOptions, type TableOptionsResolved } from '@tanstack/table-core';
|
|
2
|
-
import { type ComponentType,
|
|
2
|
+
import { type ComponentType, type VNode } from 'preact';
|
|
3
3
|
import { useEffect, useState } from 'preact/hooks';
|
|
4
4
|
|
|
5
5
|
import { usePageSizeContext } from './pagination-context';
|
|
@@ -7,7 +7,6 @@ import { useDispatchFinishedLoadingEvent } from '../../utils/useDispatchFinished
|
|
|
7
7
|
import { useLapisUrl } from '../LapisUrlContext';
|
|
8
8
|
import { ErrorBoundary } from '../components/error-boundary';
|
|
9
9
|
import { LoadingDisplay } from '../components/loading-display';
|
|
10
|
-
import { NoDataDisplay } from '../components/no-data-display';
|
|
11
10
|
import { ResizeContainer } from '../components/resize-container';
|
|
12
11
|
import { formatProportion } from '../shared/table/formatProportion';
|
|
13
12
|
import { useQuery } from '../useQuery';
|
|
@@ -49,10 +48,6 @@ export const StatisticsInner: FunctionComponent<StatisticsProps> = (componentPro
|
|
|
49
48
|
throw error;
|
|
50
49
|
}
|
|
51
50
|
|
|
52
|
-
if (data === null) {
|
|
53
|
-
return <NoDataDisplay />;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
51
|
return <MetricDataTabs data={data} />;
|
|
57
52
|
};
|
|
58
53
|
|
|
@@ -21,14 +21,5 @@ export async function fetchStringAutocompleteList({
|
|
|
21
21
|
return data
|
|
22
22
|
.map((item) => ({ count: item.count, value: item[field] }))
|
|
23
23
|
.filter((item): item is { count: number; value: string } => item.value !== null)
|
|
24
|
-
.sort((a, b) =>
|
|
25
|
-
if (a.value === null) {
|
|
26
|
-
return 1;
|
|
27
|
-
}
|
|
28
|
-
if (b.value === null) {
|
|
29
|
-
return -1;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return a.value.localeCompare(b.value);
|
|
33
|
-
});
|
|
24
|
+
.sort((a, b) => a.value.localeCompare(b.value));
|
|
34
25
|
}
|
|
@@ -8,7 +8,6 @@ import { lapisFilterSchema } from '../../types';
|
|
|
8
8
|
import { DownshiftCombobox } from '../components/downshift-combobox';
|
|
9
9
|
import { ErrorBoundary } from '../components/error-boundary';
|
|
10
10
|
import { LoadingDisplay } from '../components/loading-display';
|
|
11
|
-
import { NoDataDisplay } from '../components/no-data-display';
|
|
12
11
|
import { ResizeContainer } from '../components/resize-container';
|
|
13
12
|
import { useQuery } from '../useQuery';
|
|
14
13
|
|
|
@@ -60,10 +59,6 @@ const TextFilterInner: FunctionComponent<TextFilterInnerProps> = ({
|
|
|
60
59
|
throw error;
|
|
61
60
|
}
|
|
62
61
|
|
|
63
|
-
if (data === null) {
|
|
64
|
-
return <NoDataDisplay />;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
62
|
return <TextSelector lapisField={lapisField} value={value} placeholderText={placeholderText} data={data} />;
|
|
68
63
|
};
|
|
69
64
|
|
|
@@ -106,5 +101,5 @@ function filterByInputValue(item: SelectItem, inputValue: string | null) {
|
|
|
106
101
|
if (inputValue === null || inputValue === '') {
|
|
107
102
|
return true;
|
|
108
103
|
}
|
|
109
|
-
return item.value
|
|
104
|
+
return item.value.toLowerCase().includes(inputValue.toLowerCase());
|
|
110
105
|
}
|
|
@@ -14,13 +14,16 @@ import { ErrorBoundary } from '../../components/error-boundary';
|
|
|
14
14
|
import { Fullscreen } from '../../components/fullscreen';
|
|
15
15
|
import Info, { InfoComponentCode, InfoHeadline1, InfoParagraph } from '../../components/info';
|
|
16
16
|
import { LoadingDisplay } from '../../components/loading-display';
|
|
17
|
-
import {
|
|
17
|
+
import { MutationsOverTimeMutationsFilter } from '../../components/mutations-over-time-mutations-filter';
|
|
18
18
|
import { NoDataDisplay } from '../../components/no-data-display';
|
|
19
19
|
import { ResizeContainer } from '../../components/resize-container';
|
|
20
20
|
import { type DisplayedSegment, SegmentSelector } from '../../components/segment-selector';
|
|
21
21
|
import Tabs from '../../components/tabs';
|
|
22
22
|
import { type MutationOverTimeDataMap } from '../../mutationsOverTime/MutationOverTimeData';
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
type MutationFilter,
|
|
25
|
+
mutationOrAnnotationDoNotMatchFilter,
|
|
26
|
+
} from '../../mutationsOverTime/getFilteredMutationsOverTimeData';
|
|
24
27
|
import MutationsOverTimeGrid from '../../mutationsOverTime/mutations-over-time-grid';
|
|
25
28
|
import { pageSizesSchema } from '../../shared/tanstackTable/pagination';
|
|
26
29
|
import { PageSizeContextProvider } from '../../shared/tanstackTable/pagination-context';
|
|
@@ -97,7 +100,7 @@ function useDisplayedSegments(mutations: MutationOverTimeDataPerLocation) {
|
|
|
97
100
|
const displayedSegments = useMemo(() => {
|
|
98
101
|
const unique = [
|
|
99
102
|
...new Set(
|
|
100
|
-
mutations.flatMap(({ data }) => data.getFirstAxisKeys().map((mutation) => mutation.segment
|
|
103
|
+
mutations.flatMap(({ data }) => data.getFirstAxisKeys().map((mutation) => mutation.segment ?? '')),
|
|
101
104
|
),
|
|
102
105
|
];
|
|
103
106
|
|
|
@@ -121,7 +124,7 @@ function getFilteredMutationOverTimeData({
|
|
|
121
124
|
}: {
|
|
122
125
|
data: MutationOverTimeDataMap;
|
|
123
126
|
displayedSegments: DisplayedSegment[];
|
|
124
|
-
mutationFilterValue:
|
|
127
|
+
mutationFilterValue: MutationFilter;
|
|
125
128
|
sequenceType: SequenceType;
|
|
126
129
|
annotationProvider: ReturnType<typeof useMutationAnnotationsProvider>;
|
|
127
130
|
}): MutationOverTimeDataMap {
|
|
@@ -148,7 +151,10 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
|
|
|
148
151
|
}) => {
|
|
149
152
|
const tabsRef = useDispatchFinishedLoadingEvent();
|
|
150
153
|
|
|
151
|
-
const [mutationFilterValue, setMutationFilterValue] = useState(
|
|
154
|
+
const [mutationFilterValue, setMutationFilterValue] = useState<MutationFilter>({
|
|
155
|
+
textFilter: '',
|
|
156
|
+
annotationNameFilter: new Set(),
|
|
157
|
+
});
|
|
152
158
|
const annotationProvider = useMutationAnnotationsProvider();
|
|
153
159
|
|
|
154
160
|
const [colorScale, setColorScale] = useState<ColorScale>({ min: 0, max: 1, color: 'indigo' });
|
|
@@ -211,8 +217,8 @@ type ToolbarProps = {
|
|
|
211
217
|
data: MutationOverTimeDataPerLocation;
|
|
212
218
|
displayedSegments: DisplayedSegment[];
|
|
213
219
|
setDisplayedSegments: (segments: DisplayedSegment[]) => void;
|
|
214
|
-
mutationFilterValue:
|
|
215
|
-
setFilterValue:
|
|
220
|
+
mutationFilterValue: MutationFilter;
|
|
221
|
+
setFilterValue: Dispatch<StateUpdater<MutationFilter>>;
|
|
216
222
|
};
|
|
217
223
|
|
|
218
224
|
const Toolbar: FunctionComponent<ToolbarProps> = ({
|
|
@@ -226,7 +232,7 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
|
|
|
226
232
|
}) => {
|
|
227
233
|
return (
|
|
228
234
|
<>
|
|
229
|
-
<
|
|
235
|
+
<MutationsOverTimeMutationsFilter setFilterValue={setFilterValue} value={mutationFilterValue} />
|
|
230
236
|
<ColorScaleSelectorDropdown colorScale={colorScale} setColorScale={setColorScale} />
|
|
231
237
|
<SegmentSelector
|
|
232
238
|
displayedSegments={displayedSegments}
|
|
@@ -23,7 +23,8 @@ export type ErrorWorkerStatus =
|
|
|
23
23
|
};
|
|
24
24
|
export type WorkerStatus<Response> = LoadingWorkerStatus | SuccessWorkerStatus<Response> | ErrorWorkerStatus;
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- We need the return type
|
|
27
|
+
export function useWebWorker<Response>(messageToWorker: unknown, WorkerConstructor: new () => Worker) {
|
|
27
28
|
const [data, setData] = useState<Response | undefined>(undefined);
|
|
28
29
|
const [error, setError] = useState<Error | undefined>(undefined);
|
|
29
30
|
const [isLoading, setIsLoading] = useState(true);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type ErrorWorkerStatus, type LoadingWorkerStatus, type SuccessWorkerStatus } from './useWebWorker';
|
|
2
2
|
import { UserFacingError } from '../components/error-display';
|
|
3
3
|
|
|
4
|
-
export async function workerFunction
|
|
4
|
+
export async function workerFunction(queryFunction: () => unknown) {
|
|
5
5
|
try {
|
|
6
6
|
postMessage({ status: 'loading' } satisfies LoadingWorkerStatus);
|
|
7
7
|
|
|
@@ -10,7 +10,7 @@ export async function workerFunction<R>(queryFunction: () => R) {
|
|
|
10
10
|
postMessage({
|
|
11
11
|
status: 'success',
|
|
12
12
|
data: workerResponse,
|
|
13
|
-
} satisfies SuccessWorkerStatus<
|
|
13
|
+
} satisfies SuccessWorkerStatus<unknown>);
|
|
14
14
|
} catch (error) {
|
|
15
15
|
postMessage(
|
|
16
16
|
(error instanceof UserFacingError
|
|
@@ -83,7 +83,7 @@ function matchLocationDataAndGeoJsonFeatures(
|
|
|
83
83
|
|
|
84
84
|
const locations: Feature<GeometryObject, EnhancedGeoJsonFeatureProperties>[] = geojsonData.features.map(
|
|
85
85
|
(feature) => {
|
|
86
|
-
const name = feature
|
|
86
|
+
const name = feature.properties.name;
|
|
87
87
|
if (typeof name !== 'string') {
|
|
88
88
|
throw new Error(
|
|
89
89
|
`GeoJSON feature with id '${feature.id}' does not have 'properties.name' of type string, was: '${name}'`,
|
|
@@ -14,13 +14,13 @@ import {
|
|
|
14
14
|
type TemporalClass,
|
|
15
15
|
} from '../utils/temporalClass';
|
|
16
16
|
|
|
17
|
-
export function queryAggregatedDataOverTime
|
|
17
|
+
export function queryAggregatedDataOverTime(
|
|
18
18
|
filter: LapisFilter,
|
|
19
19
|
granularity: TemporalGranularity,
|
|
20
20
|
smoothingWindow: number,
|
|
21
|
-
lapisDateField:
|
|
21
|
+
lapisDateField: string,
|
|
22
22
|
) {
|
|
23
|
-
const fetchData = new FetchAggregatedOperator<
|
|
23
|
+
const fetchData = new FetchAggregatedOperator<Record<string, string | null>>(filter, [lapisDateField]);
|
|
24
24
|
const dataWithFixedDateKey = new RenameFieldOperator(fetchData, lapisDateField, 'date');
|
|
25
25
|
const mapData = new MapOperator(dataWithFixedDateKey, (d) => mapDateToGranularityRange(d, granularity));
|
|
26
26
|
const groupByData = new GroupByAndSumOperator(mapData, 'dateRange', 'count');
|