@genspectrum/dashboard-components 1.1.0 → 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 +31 -31
- package/dist/components.js +74 -120
- package/dist/components.js.map +1 -1
- package/dist/util.d.ts +31 -31
- 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/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/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/mutations-over-time.tsx +3 -9
- 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 +1 -1
- 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 +1444 -1456
- 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/standalone-bundle/assets/mutationOverTimeWorker-B6bf3R3j.js.map +0 -1
|
@@ -110,8 +110,8 @@ function filterByInputValue(item: SelectItem, inputValue: string | null) {
|
|
|
110
110
|
return true;
|
|
111
111
|
}
|
|
112
112
|
return (
|
|
113
|
-
item
|
|
114
|
-
item
|
|
113
|
+
!!item.label?.toLowerCase().includes(inputValue.toLowerCase()) ||
|
|
114
|
+
item.description.toLowerCase().includes(inputValue.toLowerCase())
|
|
115
115
|
);
|
|
116
116
|
}
|
|
117
117
|
|
|
@@ -143,8 +143,8 @@ function concatenateLocation(locationFilter: LapisLocationFilter, fields: string
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
function emptyLocationFilter(fields: string[]) {
|
|
146
|
-
return fields.reduce((acc, field) => {
|
|
146
|
+
return fields.reduce<LapisLocationFilter>((acc, field) => {
|
|
147
147
|
acc[field] = undefined;
|
|
148
148
|
return acc;
|
|
149
|
-
}, {}
|
|
149
|
+
}, {});
|
|
150
150
|
}
|
|
@@ -3,9 +3,7 @@ import { type Dataset } from '../../operator/Dataset';
|
|
|
3
3
|
import { type DeletionClass, type SubstitutionClass } from '../../utils/mutations';
|
|
4
4
|
import { type ProportionInterval } from '../components/proportion-selector';
|
|
5
5
|
|
|
6
|
-
type Proportions =
|
|
7
|
-
[displayName: string]: number;
|
|
8
|
-
};
|
|
6
|
+
type Proportions = Record<string, number>;
|
|
9
7
|
|
|
10
8
|
type MutationComparisonRow = {
|
|
11
9
|
mutation: SubstitutionClass | DeletionClass;
|
|
@@ -110,7 +110,7 @@ export const MutationComparisonVenn: FunctionComponent<MutationComparisonVennPro
|
|
|
110
110
|
<div className='flex-1'>
|
|
111
111
|
<GsChart configuration={config} />
|
|
112
112
|
</div>
|
|
113
|
-
<p
|
|
113
|
+
<p className='flex flex-wrap break-words m-2'>
|
|
114
114
|
<SelectedMutationsDescription
|
|
115
115
|
selectedDatasetIndex={selectedDatasetIndex}
|
|
116
116
|
sets={sets}
|
|
@@ -16,7 +16,6 @@ import Info, { InfoComponentCode, InfoHeadline1, InfoHeadline2, InfoParagraph }
|
|
|
16
16
|
import { LoadingDisplay } from '../components/loading-display';
|
|
17
17
|
import { DeletionsLink, ProportionExplanation, SubstitutionsLink } from '../components/mutation-info';
|
|
18
18
|
import { type DisplayedMutationType, MutationTypeSelector } from '../components/mutation-type-selector';
|
|
19
|
-
import { NoDataDisplay } from '../components/no-data-display';
|
|
20
19
|
import { type ProportionInterval } from '../components/proportion-selector';
|
|
21
20
|
import { ProportionSelectorDropdown } from '../components/proportion-selector-dropdown';
|
|
22
21
|
import { ResizeContainer } from '../components/resize-container';
|
|
@@ -68,10 +67,6 @@ const MutationComparisonInner: FunctionComponent<MutationComparisonProps> = (com
|
|
|
68
67
|
throw error;
|
|
69
68
|
}
|
|
70
69
|
|
|
71
|
-
if (data === null) {
|
|
72
|
-
return <NoDataDisplay />;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
70
|
return <MutationComparisonTabs data={data.mutationData} originalComponentProps={componentProps} />;
|
|
76
71
|
};
|
|
77
72
|
|
|
@@ -191,10 +191,10 @@ const exampleSegmentString = (referenceGenome: ReferenceGenome) => {
|
|
|
191
191
|
|
|
192
192
|
const exampleSegment = (referenceGenome: ReferenceGenome) => {
|
|
193
193
|
if (referenceGenome.genes.length > 0) {
|
|
194
|
-
return
|
|
194
|
+
return referenceGenome.genes[0].name;
|
|
195
195
|
}
|
|
196
196
|
if (referenceGenome.nucleotideSequences.length > 1) {
|
|
197
|
-
return
|
|
197
|
+
return referenceGenome.nucleotideSequences[0].name;
|
|
198
198
|
}
|
|
199
199
|
return '';
|
|
200
200
|
};
|
|
@@ -112,7 +112,7 @@ function MutationFilterInner({ initialValue }: MutationFilterInnerProps) {
|
|
|
112
112
|
const handleInputChange = (newInputValue: string | undefined) => {
|
|
113
113
|
setShowErrorIndicator(false);
|
|
114
114
|
if (newInputValue?.includes(',')) {
|
|
115
|
-
const values = newInputValue
|
|
115
|
+
const values = newInputValue.split(',').map((value) => {
|
|
116
116
|
return { value, parsedValue: parseAndValidateMutation(value.trim(), referenceGenome) };
|
|
117
117
|
});
|
|
118
118
|
const validEntries = values.map((value) => value.parsedValue).filter((value) => value !== null);
|
|
@@ -18,9 +18,7 @@ const accumulateByPosition = (data: SubstitutionOrDeletionEntry[], sequenceType:
|
|
|
18
18
|
const referenceBases = new Map<string, string | undefined>();
|
|
19
19
|
|
|
20
20
|
for (const mutationEntry of data) {
|
|
21
|
-
const position =
|
|
22
|
-
(mutationEntry.mutation.segment ? `${mutationEntry.mutation.segment}:` : '') +
|
|
23
|
-
mutationEntry.mutation.position;
|
|
21
|
+
const position = `${mutationEntry.mutation.segment ? `${mutationEntry.mutation.segment}:` : ''}${mutationEntry.mutation.position}`;
|
|
24
22
|
referenceBases.set(position, mutationEntry.mutation.valueAtReference);
|
|
25
23
|
|
|
26
24
|
const initiallyFillPositionsToProportionAtBase = () => {
|
|
@@ -69,9 +67,7 @@ const accumulateByPosition = (data: SubstitutionOrDeletionEntry[], sequenceType:
|
|
|
69
67
|
});
|
|
70
68
|
};
|
|
71
69
|
|
|
72
|
-
export type BasesData =
|
|
73
|
-
[base: string]: BaseCell;
|
|
74
|
-
};
|
|
70
|
+
export type BasesData = Record<string, BaseCell>;
|
|
75
71
|
export type MutationsGridDataRow = BasesData & { position: string };
|
|
76
72
|
|
|
77
73
|
const byProportion = (row: MutationsGridDataRow, proportionInterval: ProportionInterval) => {
|
|
@@ -30,7 +30,7 @@ export function getMutationsTableData(
|
|
|
30
30
|
}, new Map<string, number>());
|
|
31
31
|
|
|
32
32
|
return tableData.map((datum) => {
|
|
33
|
-
const baselineMutationCount = baselineMutationCounts.get(datum.mutation.code)
|
|
33
|
+
const baselineMutationCount = baselineMutationCounts.get(datum.mutation.code) ?? 0;
|
|
34
34
|
const jaccardSimilarity = calculateJaccardSimilarity(overallVariantCount, baselineMutationCount, datum.count);
|
|
35
35
|
|
|
36
36
|
return {
|
|
@@ -62,7 +62,7 @@ export const MutationsGrid: FunctionComponent<MutationsGridProps> = ({
|
|
|
62
62
|
},
|
|
63
63
|
formatter: (cell: BaseCell) => formatProportion(cell.proportion),
|
|
64
64
|
attributes: (cell: BaseCell, row: Row) => {
|
|
65
|
-
// grid-js: the cell and row are null for header cells
|
|
65
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- grid-js: the cell and row are null for header cells
|
|
66
66
|
if (row === null) {
|
|
67
67
|
return {};
|
|
68
68
|
}
|
|
@@ -24,7 +24,6 @@ import Info, { InfoComponentCode, InfoHeadline1, InfoParagraph } from '../compon
|
|
|
24
24
|
import { LoadingDisplay } from '../components/loading-display';
|
|
25
25
|
import { DeletionsLink, InsertionsLink, ProportionExplanation, SubstitutionsLink } from '../components/mutation-info';
|
|
26
26
|
import { type DisplayedMutationType, MutationTypeSelector } from '../components/mutation-type-selector';
|
|
27
|
-
import { NoDataDisplay } from '../components/no-data-display';
|
|
28
27
|
import type { ProportionInterval } from '../components/proportion-selector';
|
|
29
28
|
import { ProportionSelectorDropdown } from '../components/proportion-selector-dropdown';
|
|
30
29
|
import { ResizeContainer } from '../components/resize-container';
|
|
@@ -75,10 +74,6 @@ export const MutationsInner: FunctionComponent<MutationsProps> = (componentProps
|
|
|
75
74
|
throw error;
|
|
76
75
|
}
|
|
77
76
|
|
|
78
|
-
if (data === null) {
|
|
79
|
-
return <NoDataDisplay />;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
77
|
return <MutationsTabs mutationsData={data} originalComponentProps={componentProps} />;
|
|
83
78
|
};
|
|
84
79
|
|
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
} from './getFilteredMutationsOverTimeData';
|
|
13
13
|
import { type MutationOverTimeWorkerResponse } from './mutationOverTimeWorker';
|
|
14
14
|
import MutationsOverTimeGrid from './mutations-over-time-grid';
|
|
15
|
-
import { type MutationOverTimeQuery } from '../../query/queryMutationsOverTime';
|
|
16
15
|
import {
|
|
17
16
|
lapisFilterSchema,
|
|
18
17
|
sequenceTypeSchema,
|
|
@@ -91,7 +90,7 @@ export const MutationsOverTimeInner: FunctionComponent<MutationsOverTimeProps> =
|
|
|
91
90
|
};
|
|
92
91
|
}, [granularity, lapis, lapisDateField, lapisFilter, sequenceType]);
|
|
93
92
|
|
|
94
|
-
const { data, error, isLoading } = useWebWorker<
|
|
93
|
+
const { data, error, isLoading } = useWebWorker<MutationOverTimeWorkerResponse>(
|
|
95
94
|
messageToWorker,
|
|
96
95
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
97
96
|
MutationOverTimeWorker,
|
|
@@ -105,7 +104,7 @@ export const MutationsOverTimeInner: FunctionComponent<MutationsOverTimeProps> =
|
|
|
105
104
|
throw error;
|
|
106
105
|
}
|
|
107
106
|
|
|
108
|
-
if (data ===
|
|
107
|
+
if (data === undefined || data.overallMutationData.length === 0) {
|
|
109
108
|
return <NoDataDisplay />;
|
|
110
109
|
}
|
|
111
110
|
|
|
@@ -173,13 +172,8 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
|
|
|
173
172
|
]);
|
|
174
173
|
|
|
175
174
|
const getTab = (view: MutationsOverTimeView) => {
|
|
176
|
-
if (filteredData === undefined) {
|
|
177
|
-
return {
|
|
178
|
-
title: 'Calculating',
|
|
179
|
-
content: <LoadingDisplay />,
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
175
|
switch (view) {
|
|
176
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- for extensibility
|
|
183
177
|
case 'grid':
|
|
184
178
|
return {
|
|
185
179
|
title: 'Grid',
|
|
@@ -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
|
}
|
|
@@ -100,7 +100,7 @@ function useDisplayedSegments(mutations: MutationOverTimeDataPerLocation) {
|
|
|
100
100
|
const displayedSegments = useMemo(() => {
|
|
101
101
|
const unique = [
|
|
102
102
|
...new Set(
|
|
103
|
-
mutations.flatMap(({ data }) => data.getFirstAxisKeys().map((mutation) => mutation.segment
|
|
103
|
+
mutations.flatMap(({ data }) => data.getFirstAxisKeys().map((mutation) => mutation.segment ?? '')),
|
|
104
104
|
),
|
|
105
105
|
];
|
|
106
106
|
|
|
@@ -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');
|
|
@@ -231,18 +231,14 @@ describe('queryMutationsOverTime', () => {
|
|
|
231
231
|
minProportion: 0.001,
|
|
232
232
|
},
|
|
233
233
|
response: {
|
|
234
|
-
data: [
|
|
235
|
-
getSomeTestMutation(0.1, 1),
|
|
236
|
-
getSomeTestMutation(0.3, 3),
|
|
237
|
-
getSomeOtherTestMutation(0.4, 4),
|
|
238
|
-
],
|
|
234
|
+
data: [getSomeTestMutation(0.2, 4), getSomeOtherTestMutation(0.4, 4)],
|
|
239
235
|
},
|
|
240
236
|
},
|
|
241
237
|
],
|
|
242
238
|
'nucleotide',
|
|
243
239
|
);
|
|
244
240
|
|
|
245
|
-
const { mutationOverTimeData } = await queryMutationsOverTimeData({
|
|
241
|
+
const { mutationOverTimeData, overallMutationData } = await queryMutationsOverTimeData({
|
|
246
242
|
lapisFilter,
|
|
247
243
|
sequenceType: 'nucleotide',
|
|
248
244
|
lapis: DUMMY_LAPIS_URL,
|
|
@@ -263,6 +259,8 @@ describe('queryMutationsOverTime', () => {
|
|
|
263
259
|
],
|
|
264
260
|
]);
|
|
265
261
|
|
|
262
|
+
expect(overallMutationData.length).toBe(2);
|
|
263
|
+
|
|
266
264
|
const sequences = mutationOverTimeData.getFirstAxisKeys();
|
|
267
265
|
expect(sequences[0].code).toBe('otherSequenceName:G234C');
|
|
268
266
|
expect(sequences[1].code).toBe('sequenceName:A123T');
|
|
@@ -435,14 +433,14 @@ describe('queryMutationsOverTime', () => {
|
|
|
435
433
|
minProportion: 0.001,
|
|
436
434
|
},
|
|
437
435
|
response: {
|
|
438
|
-
data: [getSomeTestMutation(0.
|
|
436
|
+
data: [getSomeTestMutation(0.25, 5)],
|
|
439
437
|
},
|
|
440
438
|
},
|
|
441
439
|
],
|
|
442
440
|
'nucleotide',
|
|
443
441
|
);
|
|
444
442
|
|
|
445
|
-
const { mutationOverTimeData } = await queryMutationsOverTimeData({
|
|
443
|
+
const { mutationOverTimeData, overallMutationData } = await queryMutationsOverTimeData({
|
|
446
444
|
lapisFilter,
|
|
447
445
|
sequenceType: 'nucleotide',
|
|
448
446
|
lapis: DUMMY_LAPIS_URL,
|
|
@@ -450,6 +448,8 @@ describe('queryMutationsOverTime', () => {
|
|
|
450
448
|
granularity: 'day',
|
|
451
449
|
});
|
|
452
450
|
|
|
451
|
+
expect(overallMutationData.length).toBe(1);
|
|
452
|
+
|
|
453
453
|
expect(mutationOverTimeData.getAsArray()).to.deep.equal([
|
|
454
454
|
[
|
|
455
455
|
{ type: 'value', proportion: 0.2, count: 2, totalCount: 11 },
|
|
@@ -527,7 +527,7 @@ describe('queryMutationsOverTime', () => {
|
|
|
527
527
|
minProportion: 0.001,
|
|
528
528
|
},
|
|
529
529
|
response: {
|
|
530
|
-
data: [getSomeTestMutation(0.
|
|
530
|
+
data: [getSomeTestMutation(0.15, 3)],
|
|
531
531
|
},
|
|
532
532
|
},
|
|
533
533
|
],
|