@genspectrum/dashboard-components 1.1.0 → 1.3.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 +22 -3
- package/dist/{NumberRangeFilterChangedEvent-B64OQZjX.js → NumberRangeFilterChangedEvent-CQ32Qy8D.js} +2 -2
- package/dist/NumberRangeFilterChangedEvent-CQ32Qy8D.js.map +1 -0
- package/dist/assets/mutationOverTimeWorker-C7saVShx.js.map +1 -0
- package/dist/components.d.ts +33 -27
- package/dist/components.js +119 -127
- package/dist/components.js.map +1 -1
- package/dist/util.d.ts +27 -27
- package/dist/util.js +1 -1
- package/package.json +7 -3
- package/src/lapisApi/lapisApi.ts +31 -2
- package/src/lapisApi/lapisTypes.ts +35 -1
- 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/__mockData__/aminoAcidMutationsByDay.ts +1 -0
- package/src/preact/mutationsOverTime/__mockData__/byWeek.ts +1 -0
- package/src/preact/mutationsOverTime/__mockData__/defaultMockData.ts +1 -0
- package/src/preact/mutationsOverTime/__mockData__/noDataWhenNoMutationsAreInFilter.ts +1 -0
- package/src/preact/mutationsOverTime/__mockData__/noDataWhenThereAreNoDatesInFilter.ts +1 -0
- package/src/preact/mutationsOverTime/__mockData__/showsMessageWhenTooManyMutations.ts +1 -0
- package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +2 -0
- package/src/preact/mutationsOverTime/mutations-over-time.tsx +11 -11
- 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 +121 -37
- package/src/query/queryMutationsOverTimeNewEndpoint.spec.ts +935 -0
- 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-over-time.spec-d.ts +3 -0
- package/src/web-components/visualization/gs-mutations-over-time.stories.ts +3 -0
- package/src/web-components/visualization/gs-mutations-over-time.tsx +9 -0
- 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-DRRi3aMG.js.map +1 -0
- package/standalone-bundle/dashboard-components.js +4454 -4430
- 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
|
@@ -12,9 +12,7 @@ type Position = {
|
|
|
12
12
|
end: number;
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
type CDSMap = {
|
|
16
|
-
[id: string]: { positions: Position[]; label: string };
|
|
17
|
-
};
|
|
15
|
+
type CDSMap = Record<string, { positions: Position[]; label: string }>;
|
|
18
16
|
|
|
19
17
|
export async function loadGff3(gff3Source: string, genomeLength: number | undefined) {
|
|
20
18
|
try {
|
|
@@ -25,9 +23,7 @@ export async function loadGff3(gff3Source: string, genomeLength: number | undefi
|
|
|
25
23
|
|
|
26
24
|
const response = await fetch(gff3Source);
|
|
27
25
|
const content = await response.text();
|
|
28
|
-
|
|
29
|
-
genomeLength = loadGenomeLength(content);
|
|
30
|
-
}
|
|
26
|
+
genomeLength ??= loadGenomeLength(content);
|
|
31
27
|
return { features: parseGFF3(content), length: genomeLength };
|
|
32
28
|
}
|
|
33
29
|
|
|
@@ -89,7 +85,7 @@ function getCDSMap(lines: string[], genome_type: string, geneMap: CDSMap): CDSMa
|
|
|
89
85
|
}
|
|
90
86
|
|
|
91
87
|
const attrPairs = getAttributes(attributes);
|
|
92
|
-
const labelAttribute = attrPairs.get('Name')
|
|
88
|
+
const labelAttribute = attrPairs.get('Name') ?? attrPairs.get('gene') ?? attrPairs.get('gene_name');
|
|
93
89
|
if (!labelAttribute) {
|
|
94
90
|
throw new UserFacingError(
|
|
95
91
|
'Invalid gff3 source',
|
|
@@ -97,11 +93,12 @@ function getCDSMap(lines: string[], genome_type: string, geneMap: CDSMap): CDSMa
|
|
|
97
93
|
);
|
|
98
94
|
}
|
|
99
95
|
const label = removeQuotes(labelAttribute);
|
|
100
|
-
const id = removeQuotes(attrPairs.get('ID')
|
|
96
|
+
const id = removeQuotes(attrPairs.get('ID') ?? labelAttribute);
|
|
101
97
|
const parentAttribute = attrPairs.get('Parent');
|
|
102
98
|
if (parentAttribute) {
|
|
103
99
|
const parent = removeQuotes(parentAttribute);
|
|
104
100
|
if (parent && parent in geneMap) {
|
|
101
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
105
102
|
delete geneMap[parent];
|
|
106
103
|
}
|
|
107
104
|
}
|
|
@@ -99,5 +99,5 @@ function filterByInputValue(item: LineageItem, inputValue: string | null) {
|
|
|
99
99
|
if (inputValue === null || inputValue === '') {
|
|
100
100
|
return true;
|
|
101
101
|
}
|
|
102
|
-
return item.lineage
|
|
102
|
+
return item.lineage.toLowerCase().includes(inputValue.toLowerCase() || '');
|
|
103
103
|
}
|
|
@@ -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
|
|
|
@@ -36,6 +36,7 @@ const meta: Meta<MutationsOverTimeProps> = {
|
|
|
36
36
|
displayMutations: { control: 'object' },
|
|
37
37
|
initialMeanProportionInterval: { control: 'object' },
|
|
38
38
|
pageSizes: { control: 'object' },
|
|
39
|
+
useNewEndpoint: { control: 'boolean' },
|
|
39
40
|
},
|
|
40
41
|
parameters: {
|
|
41
42
|
fetchMock: {},
|
|
@@ -80,6 +81,7 @@ export const Default: StoryObj<MutationsOverTimeProps> = {
|
|
|
80
81
|
granularity: 'month',
|
|
81
82
|
lapisDateField: 'date',
|
|
82
83
|
initialMeanProportionInterval: { min: 0.05, max: 0.9 },
|
|
84
|
+
useNewEndpoint: false,
|
|
83
85
|
pageSizes: [10, 20, 30, 40, 50],
|
|
84
86
|
},
|
|
85
87
|
};
|
|
@@ -53,6 +53,7 @@ const mutationOverTimeSchema = z.object({
|
|
|
53
53
|
views: z.array(mutationsOverTimeViewSchema),
|
|
54
54
|
granularity: temporalGranularitySchema,
|
|
55
55
|
lapisDateField: z.string().min(1),
|
|
56
|
+
useNewEndpoint: z.boolean().optional(),
|
|
56
57
|
displayMutations: displayMutationsSchema.optional(),
|
|
57
58
|
initialMeanProportionInterval: z.object({
|
|
58
59
|
min: z.number().min(0).max(1),
|
|
@@ -77,21 +78,25 @@ export const MutationsOverTime: FunctionComponent<MutationsOverTimeProps> = (com
|
|
|
77
78
|
);
|
|
78
79
|
};
|
|
79
80
|
|
|
80
|
-
export const MutationsOverTimeInner: FunctionComponent<MutationsOverTimeProps> = (
|
|
81
|
+
export const MutationsOverTimeInner: FunctionComponent<MutationsOverTimeProps> = ({
|
|
82
|
+
useNewEndpoint = false,
|
|
83
|
+
...componentProps
|
|
84
|
+
}) => {
|
|
81
85
|
const lapis = useLapisUrl();
|
|
82
86
|
const { lapisFilter, sequenceType, granularity, lapisDateField } = componentProps;
|
|
83
87
|
|
|
84
|
-
const messageToWorker = useMemo(() => {
|
|
88
|
+
const messageToWorker: MutationOverTimeQuery = useMemo(() => {
|
|
85
89
|
return {
|
|
86
90
|
lapisFilter,
|
|
87
91
|
sequenceType,
|
|
88
92
|
granularity,
|
|
89
93
|
lapisDateField,
|
|
90
94
|
lapis,
|
|
95
|
+
useNewEndpoint,
|
|
91
96
|
};
|
|
92
|
-
}, [granularity, lapis, lapisDateField, lapisFilter, sequenceType]);
|
|
97
|
+
}, [granularity, lapis, lapisDateField, lapisFilter, sequenceType, useNewEndpoint]);
|
|
93
98
|
|
|
94
|
-
const { data, error, isLoading } = useWebWorker<
|
|
99
|
+
const { data, error, isLoading } = useWebWorker<MutationOverTimeWorkerResponse>(
|
|
95
100
|
messageToWorker,
|
|
96
101
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
97
102
|
MutationOverTimeWorker,
|
|
@@ -105,7 +110,7 @@ export const MutationsOverTimeInner: FunctionComponent<MutationsOverTimeProps> =
|
|
|
105
110
|
throw error;
|
|
106
111
|
}
|
|
107
112
|
|
|
108
|
-
if (data ===
|
|
113
|
+
if (data === undefined || data.overallMutationData.length === 0) {
|
|
109
114
|
return <NoDataDisplay />;
|
|
110
115
|
}
|
|
111
116
|
|
|
@@ -173,13 +178,8 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
|
|
|
173
178
|
]);
|
|
174
179
|
|
|
175
180
|
const getTab = (view: MutationsOverTimeView) => {
|
|
176
|
-
if (filteredData === undefined) {
|
|
177
|
-
return {
|
|
178
|
-
title: 'Calculating',
|
|
179
|
-
content: <LoadingDisplay />,
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
181
|
switch (view) {
|
|
182
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- for extensibility
|
|
183
183
|
case 'grid':
|
|
184
184
|
return {
|
|
185
185
|
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
|
}
|