@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.
Files changed (104) hide show
  1. package/custom-elements.json +2 -2
  2. package/dist/{NumberRangeFilterChangedEvent-B64OQZjX.js → NumberRangeFilterChangedEvent-CQ32Qy8D.js} +2 -2
  3. package/dist/NumberRangeFilterChangedEvent-CQ32Qy8D.js.map +1 -0
  4. package/dist/assets/mutationOverTimeWorker-DpW4YOGl.js.map +1 -0
  5. package/dist/components.d.ts +29 -29
  6. package/dist/components.js +186 -145
  7. package/dist/components.js.map +1 -1
  8. package/dist/util.d.ts +29 -29
  9. package/dist/util.js +1 -1
  10. package/package.json +7 -3
  11. package/src/lapisApi/lapisApi.ts +2 -2
  12. package/src/operator/DivisionOperator.ts +4 -2
  13. package/src/operator/FetchDetailsOperator.ts +1 -1
  14. package/src/operator/RenameFieldOperator.ts +3 -3
  15. package/src/preact/MutationAnnotationsContext.tsx +15 -7
  16. package/src/preact/aggregatedData/aggregate.tsx +0 -5
  17. package/src/preact/components/annotated-mutation.tsx +0 -1
  18. package/src/preact/components/clearable-select.stories.tsx +1 -1
  19. package/src/preact/components/confidence-interval-selector.tsx +1 -1
  20. package/src/preact/components/error-boundary.tsx +1 -5
  21. package/src/preact/components/error-display.tsx +1 -1
  22. package/src/preact/components/fullscreen.tsx +2 -5
  23. package/src/preact/components/info.stories.tsx +1 -1
  24. package/src/preact/components/min-max-range-slider.tsx +1 -1
  25. package/src/preact/components/mutations-over-time-mutations-filter.stories.tsx +109 -0
  26. package/src/preact/components/mutations-over-time-mutations-filter.tsx +139 -0
  27. package/src/preact/components/proportion-selector.tsx +4 -4
  28. package/src/preact/components/select.tsx +1 -1
  29. package/src/preact/components/table.tsx +1 -1
  30. package/src/preact/components/tabs.tsx +1 -1
  31. package/src/preact/components/tooltip.stories.tsx +1 -1
  32. package/src/preact/components/tooltip.tsx +1 -1
  33. package/src/preact/genomeViewer/CDSPlot.tsx +3 -3
  34. package/src/preact/genomeViewer/loadGff3.ts +5 -8
  35. package/src/preact/lineageFilter/lineage-filter.tsx +1 -1
  36. package/src/preact/locationFilter/location-filter.tsx +4 -4
  37. package/src/preact/mutationComparison/getMutationComparisonTableData.ts +1 -3
  38. package/src/preact/mutationComparison/mutation-comparison-venn.tsx +1 -1
  39. package/src/preact/mutationComparison/mutation-comparison.tsx +0 -5
  40. package/src/preact/mutationFilter/mutation-filter-info.tsx +2 -2
  41. package/src/preact/mutationFilter/mutation-filter.tsx +1 -1
  42. package/src/preact/mutations/getMutationsGridData.ts +2 -6
  43. package/src/preact/mutations/getMutationsTableData.ts +1 -1
  44. package/src/preact/mutations/mutations-grid.tsx +1 -1
  45. package/src/preact/mutations/mutations.tsx +0 -5
  46. package/src/preact/mutationsOverTime/getFilteredMutationsOverTime.spec.ts +27 -16
  47. package/src/preact/mutationsOverTime/getFilteredMutationsOverTimeData.ts +45 -11
  48. package/src/preact/mutationsOverTime/mutations-over-time.tsx +16 -15
  49. package/src/preact/numberRangeFilter/number-range-filter.tsx +4 -4
  50. package/src/preact/numberSequencesOverTime/getNumberOfSequencesOverTimeTableData.ts +1 -4
  51. package/src/preact/numberSequencesOverTime/number-sequences-over-time.tsx +0 -5
  52. package/src/preact/prevalenceOverTime/prevalence-over-time-bar-chart.tsx +1 -1
  53. package/src/preact/prevalenceOverTime/prevalence-over-time-bubble-chart.tsx +1 -1
  54. package/src/preact/prevalenceOverTime/prevalence-over-time-line-chart.tsx +1 -1
  55. package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +1 -1
  56. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage-chart.tsx +5 -5
  57. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +1 -4
  58. package/src/preact/sequencesByLocation/loadMapSource.tsx +5 -2
  59. package/src/preact/shared/aspectRatio/AspectRatio.tsx +1 -1
  60. package/src/preact/shared/floating-ui/hooks.ts +2 -2
  61. package/src/preact/shared/sort/sortMutationPositions.ts +2 -2
  62. package/src/preact/shared/tanstackTable/pagination.tsx +2 -2
  63. package/src/preact/shared/tanstackTable/tanstackTable.tsx +1 -1
  64. package/src/preact/statistic/statistics.tsx +0 -5
  65. package/src/preact/textFilter/fetchStringAutocompleteList.ts +1 -10
  66. package/src/preact/textFilter/text-filter.tsx +1 -6
  67. package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.tsx +14 -8
  68. package/src/preact/webWorkers/useWebWorker.ts +2 -1
  69. package/src/preact/webWorkers/workerFunction.ts +2 -2
  70. package/src/query/computeMapLocationData.ts +1 -1
  71. package/src/query/queryAggregatedDataOverTime.ts +3 -3
  72. package/src/query/queryMutationsOverTime.spec.ts +9 -9
  73. package/src/query/queryMutationsOverTime.ts +22 -16
  74. package/src/query/queryRelativeGrowthAdvantage.ts +5 -9
  75. package/src/query/queryWastewaterMutationsOverTime.ts +1 -1
  76. package/src/types.ts +1 -1
  77. package/src/utils/mutations.ts +10 -10
  78. package/src/utils/type-utils.ts +1 -1
  79. package/src/utils/typeAssertions.spec.ts +1 -1
  80. package/src/web-components/gs-app.spec-d.ts +1 -1
  81. package/src/web-components/gs-app.stories.ts +1 -1
  82. package/src/web-components/input/gs-date-range-filter.tsx +2 -2
  83. package/src/web-components/input/gs-lineage-filter.tsx +2 -2
  84. package/src/web-components/input/gs-location-filter.tsx +3 -3
  85. package/src/web-components/input/gs-mutation-filter.tsx +2 -2
  86. package/src/web-components/input/gs-number-range-filter.spec.ts +1 -1
  87. package/src/web-components/input/gs-text-filter.tsx +2 -2
  88. package/src/web-components/visualization/gs-aggregate.tsx +2 -2
  89. package/src/web-components/visualization/gs-genome-data-viewer.spec-d.ts +1 -1
  90. package/src/web-components/visualization/gs-mutation-comparison.tsx +2 -2
  91. package/src/web-components/visualization/gs-mutations.tsx +2 -2
  92. package/src/web-components/visualization/gs-number-sequences-over-time.tsx +2 -2
  93. package/src/web-components/visualization/gs-prevalence-over-time.tsx +2 -2
  94. package/src/web-components/visualization/gs-relative-growth-advantage.tsx +2 -2
  95. package/src/web-components/visualization/gs-sequences-by-location.tsx +2 -2
  96. package/src/web-components/visualization/gs-statistics.tsx +2 -2
  97. package/standalone-bundle/assets/mutationOverTimeWorker-CZVvQBze.js.map +1 -0
  98. package/standalone-bundle/dashboard-components.js +3989 -3923
  99. package/standalone-bundle/dashboard-components.js.map +1 -1
  100. package/dist/NumberRangeFilterChangedEvent-B64OQZjX.js.map +0 -1
  101. package/dist/assets/mutationOverTimeWorker-DjH04AQB.js.map +0 -1
  102. package/src/preact/components/mutations-over-time-text-filter.stories.tsx +0 -57
  103. package/src/preact/components/mutations-over-time-text-filter.tsx +0 -63
  104. 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: string;
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
- filterValue: string,
80
+ mutationFilter: MutationFilter,
76
81
  annotationProvider: ReturnType<typeof useMutationAnnotationsProvider>,
77
82
  ) {
78
- if (filterValue === '') {
79
- return false;
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(filterValue)) {
83
- return false;
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 true;
105
+ return false;
89
106
  }
90
- return !mutationAnnotations.some(
107
+ return mutationAnnotations.some(
91
108
  (annotation) =>
92
- annotation.description.includes(filterValue) ||
93
- annotation.name.includes(filterValue) ||
94
- annotation.symbol.includes(filterValue),
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 { displayMutationsSchema, getFilteredMutationOverTimeData } from './getFilteredMutationsOverTimeData';
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 { MutationsOverTimeTextFilter } from '../components/mutations-over-time-text-filter';
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<MutationOverTimeQuery, MutationOverTimeWorkerResponse>(
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 === null || data === undefined || data.overallMutationData.length === 0) {
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: string;
231
- setFilterValue: (filterValue: string) => void;
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
- <MutationsOverTimeTextFilter setFilterValue={setFilterValue} value={mutationFilterValue} />
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
- inputmode='numeric'
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
- inputmode='numeric'
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> = { [K in DateRangeKey]: string } & { [key: string]: number };
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?.[yAxisScaleType])
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?.[yAxisScaleType])
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?.[yAxisScaleType])
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 === null || data.every((variant) => variant.content.length === 0)) {
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?.[yAxisScaleType])
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 class='mx-auto flex items-end flex-wrap'>
96
- <span class='text-[#606060]'>Relative advantage:</span>
95
+ <div className='mx-auto flex items-end flex-wrap'>
96
+ <span className='text-[#606060]'>Relative advantage:</span>
97
97
  <div>
98
- <span class='text-2xl ml-3'> {formatProportion(relativeAdvantage)} </span>
99
- <span class='ml-2.5'>
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?.objects[mapSource.topologyObjectsKey] as GeometryCollection<GeoJsonFeatureProperties>;
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)?.substring(0, 100)}'`,
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 class={`w-full relative`} style={{ paddingTop: `${aspectRatio}%` }}>
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?: Array<Middleware | null | undefined | false>,
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 = a.match(pattern);
5
- const bMatch = b.match(pattern);
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?.value);
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?.value} could not be parsed as a number.`,
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, h, type VNode } from 'preact';
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?.toLowerCase().includes(inputValue?.toLowerCase() || '');
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 { MutationsOverTimeTextFilter } from '../../components/mutations-over-time-text-filter';
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 { mutationOrAnnotationDoNotMatchFilter } from '../../mutationsOverTime/getFilteredMutationsOverTimeData';
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: string;
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: string;
215
- setFilterValue: (filterValue: string) => void;
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
- <MutationsOverTimeTextFilter setFilterValue={setFilterValue} value={mutationFilterValue} />
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
- export function useWebWorker<Request, Response>(messageToWorker: Request, WorkerConstructor: new () => Worker) {
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<R>(queryFunction: () => R) {
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<R>);
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?.properties?.name;
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<LapisDateField extends string>(
17
+ export function queryAggregatedDataOverTime(
18
18
  filter: LapisFilter,
19
19
  granularity: TemporalGranularity,
20
20
  smoothingWindow: number,
21
- lapisDateField: LapisDateField,
21
+ lapisDateField: string,
22
22
  ) {
23
- const fetchData = new FetchAggregatedOperator<{ [key in LapisDateField]: string | null }>(filter, [lapisDateField]);
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');