@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.
Files changed (109) hide show
  1. package/custom-elements.json +22 -3
  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-C7saVShx.js.map +1 -0
  5. package/dist/components.d.ts +33 -27
  6. package/dist/components.js +119 -127
  7. package/dist/components.js.map +1 -1
  8. package/dist/util.d.ts +27 -27
  9. package/dist/util.js +1 -1
  10. package/package.json +7 -3
  11. package/src/lapisApi/lapisApi.ts +31 -2
  12. package/src/lapisApi/lapisTypes.ts +35 -1
  13. package/src/operator/DivisionOperator.ts +4 -2
  14. package/src/operator/FetchDetailsOperator.ts +1 -1
  15. package/src/operator/RenameFieldOperator.ts +3 -3
  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/proportion-selector.tsx +4 -4
  26. package/src/preact/components/select.tsx +1 -1
  27. package/src/preact/components/table.tsx +1 -1
  28. package/src/preact/components/tabs.tsx +1 -1
  29. package/src/preact/components/tooltip.stories.tsx +1 -1
  30. package/src/preact/components/tooltip.tsx +1 -1
  31. package/src/preact/genomeViewer/CDSPlot.tsx +3 -3
  32. package/src/preact/genomeViewer/loadGff3.ts +5 -8
  33. package/src/preact/lineageFilter/lineage-filter.tsx +1 -1
  34. package/src/preact/locationFilter/location-filter.tsx +4 -4
  35. package/src/preact/mutationComparison/getMutationComparisonTableData.ts +1 -3
  36. package/src/preact/mutationComparison/mutation-comparison-venn.tsx +1 -1
  37. package/src/preact/mutationComparison/mutation-comparison.tsx +0 -5
  38. package/src/preact/mutationFilter/mutation-filter-info.tsx +2 -2
  39. package/src/preact/mutationFilter/mutation-filter.tsx +1 -1
  40. package/src/preact/mutations/getMutationsGridData.ts +2 -6
  41. package/src/preact/mutations/getMutationsTableData.ts +1 -1
  42. package/src/preact/mutations/mutations-grid.tsx +1 -1
  43. package/src/preact/mutations/mutations.tsx +0 -5
  44. package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutationsByDay.ts +1 -0
  45. package/src/preact/mutationsOverTime/__mockData__/byWeek.ts +1 -0
  46. package/src/preact/mutationsOverTime/__mockData__/defaultMockData.ts +1 -0
  47. package/src/preact/mutationsOverTime/__mockData__/noDataWhenNoMutationsAreInFilter.ts +1 -0
  48. package/src/preact/mutationsOverTime/__mockData__/noDataWhenThereAreNoDatesInFilter.ts +1 -0
  49. package/src/preact/mutationsOverTime/__mockData__/showsMessageWhenTooManyMutations.ts +1 -0
  50. package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +2 -0
  51. package/src/preact/mutationsOverTime/mutations-over-time.tsx +11 -11
  52. package/src/preact/numberRangeFilter/number-range-filter.tsx +4 -4
  53. package/src/preact/numberSequencesOverTime/getNumberOfSequencesOverTimeTableData.ts +1 -4
  54. package/src/preact/numberSequencesOverTime/number-sequences-over-time.tsx +0 -5
  55. package/src/preact/prevalenceOverTime/prevalence-over-time-bar-chart.tsx +1 -1
  56. package/src/preact/prevalenceOverTime/prevalence-over-time-bubble-chart.tsx +1 -1
  57. package/src/preact/prevalenceOverTime/prevalence-over-time-line-chart.tsx +1 -1
  58. package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +1 -1
  59. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage-chart.tsx +5 -5
  60. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +1 -4
  61. package/src/preact/sequencesByLocation/loadMapSource.tsx +5 -2
  62. package/src/preact/shared/aspectRatio/AspectRatio.tsx +1 -1
  63. package/src/preact/shared/floating-ui/hooks.ts +2 -2
  64. package/src/preact/shared/sort/sortMutationPositions.ts +2 -2
  65. package/src/preact/shared/tanstackTable/pagination.tsx +2 -2
  66. package/src/preact/shared/tanstackTable/tanstackTable.tsx +1 -1
  67. package/src/preact/statistic/statistics.tsx +0 -5
  68. package/src/preact/textFilter/fetchStringAutocompleteList.ts +1 -10
  69. package/src/preact/textFilter/text-filter.tsx +1 -6
  70. package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.tsx +1 -1
  71. package/src/preact/webWorkers/useWebWorker.ts +2 -1
  72. package/src/preact/webWorkers/workerFunction.ts +2 -2
  73. package/src/query/computeMapLocationData.ts +1 -1
  74. package/src/query/queryAggregatedDataOverTime.ts +3 -3
  75. package/src/query/queryMutationsOverTime.spec.ts +9 -9
  76. package/src/query/queryMutationsOverTime.ts +121 -37
  77. package/src/query/queryMutationsOverTimeNewEndpoint.spec.ts +935 -0
  78. package/src/query/queryRelativeGrowthAdvantage.ts +5 -9
  79. package/src/query/queryWastewaterMutationsOverTime.ts +1 -1
  80. package/src/types.ts +1 -1
  81. package/src/utils/mutations.ts +10 -10
  82. package/src/utils/type-utils.ts +1 -1
  83. package/src/utils/typeAssertions.spec.ts +1 -1
  84. package/src/web-components/gs-app.spec-d.ts +1 -1
  85. package/src/web-components/gs-app.stories.ts +1 -1
  86. package/src/web-components/input/gs-date-range-filter.tsx +2 -2
  87. package/src/web-components/input/gs-lineage-filter.tsx +2 -2
  88. package/src/web-components/input/gs-location-filter.tsx +3 -3
  89. package/src/web-components/input/gs-mutation-filter.tsx +2 -2
  90. package/src/web-components/input/gs-number-range-filter.spec.ts +1 -1
  91. package/src/web-components/input/gs-text-filter.tsx +2 -2
  92. package/src/web-components/visualization/gs-aggregate.tsx +2 -2
  93. package/src/web-components/visualization/gs-genome-data-viewer.spec-d.ts +1 -1
  94. package/src/web-components/visualization/gs-mutation-comparison.tsx +2 -2
  95. package/src/web-components/visualization/gs-mutations-over-time.spec-d.ts +3 -0
  96. package/src/web-components/visualization/gs-mutations-over-time.stories.ts +3 -0
  97. package/src/web-components/visualization/gs-mutations-over-time.tsx +9 -0
  98. package/src/web-components/visualization/gs-mutations.tsx +2 -2
  99. package/src/web-components/visualization/gs-number-sequences-over-time.tsx +2 -2
  100. package/src/web-components/visualization/gs-prevalence-over-time.tsx +2 -2
  101. package/src/web-components/visualization/gs-relative-growth-advantage.tsx +2 -2
  102. package/src/web-components/visualization/gs-sequences-by-location.tsx +2 -2
  103. package/src/web-components/visualization/gs-statistics.tsx +2 -2
  104. package/standalone-bundle/assets/mutationOverTimeWorker-DRRi3aMG.js.map +1 -0
  105. package/standalone-bundle/dashboard-components.js +4454 -4430
  106. package/standalone-bundle/dashboard-components.js.map +1 -1
  107. package/dist/NumberRangeFilterChangedEvent-B64OQZjX.js.map +0 -1
  108. package/dist/assets/mutationOverTimeWorker-DjH04AQB.js.map +0 -1
  109. 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
- if (!genomeLength) {
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') || attrPairs.get('gene') || attrPairs.get('gene_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') || labelAttribute);
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?.toLowerCase().includes(inputValue?.toLowerCase() || '');
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?.label?.toLowerCase().includes(inputValue.toLowerCase()) ||
114
- item?.description.toLowerCase().includes(inputValue.toLowerCase())
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
- }, {} as LapisLocationFilter);
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 class='flex flex-wrap break-words m-2'>
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 `${referenceGenome.genes[0].name}`;
194
+ return referenceGenome.genes[0].name;
195
195
  }
196
196
  if (referenceGenome.nucleotideSequences.length > 1) {
197
- return `${referenceGenome.nucleotideSequences[0].name}`;
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?.split(',').map((value) => {
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) || 0;
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
 
@@ -11,6 +11,7 @@ export const aminoAcidMutationsByDay: MutationOverTimeMockData = {
11
11
  granularity: 'day',
12
12
  lapisDateField: 'date',
13
13
  lapis: 'https://lapis.cov-spectrum.org/open/v2',
14
+ useNewEndpoint: false,
14
15
  },
15
16
  response: {
16
17
  overallMutationData: [
@@ -11,6 +11,7 @@ export const byWeek: MutationOverTimeMockData = {
11
11
  granularity: 'week',
12
12
  lapisDateField: 'date',
13
13
  lapis: 'https://lapis.cov-spectrum.org/open/v2',
14
+ useNewEndpoint: false,
14
15
  },
15
16
  response: {
16
17
  overallMutationData: [
@@ -11,6 +11,7 @@ export const defaultMockData: MutationOverTimeMockData = {
11
11
  granularity: 'month',
12
12
  lapisDateField: 'date',
13
13
  lapis: 'https://lapis.cov-spectrum.org/open/v2',
14
+ useNewEndpoint: false,
14
15
  },
15
16
  response: {
16
17
  overallMutationData: [
@@ -10,6 +10,7 @@ export const noDataWhenNoMutationsAreInFilter: MutationOverTimeMockData = {
10
10
  granularity: 'year',
11
11
  lapisDateField: 'date',
12
12
  lapis: 'https://lapis.cov-spectrum.org/open/v2',
13
+ useNewEndpoint: false,
13
14
  },
14
15
  response: {
15
16
  overallMutationData: [],
@@ -10,6 +10,7 @@ export const noDataWhenNoMutationsAreInFilter: MutationOverTimeMockData = {
10
10
  granularity: 'year',
11
11
  lapisDateField: 'date',
12
12
  lapis: 'https://lapis.cov-spectrum.org/open/v2',
13
+ useNewEndpoint: false,
13
14
  },
14
15
  response: {
15
16
  overallMutationData: [],
@@ -10,6 +10,7 @@ export const showsMessageWhenTooManyMutations: MutationOverTimeMockData = {
10
10
  granularity: 'year',
11
11
  lapisDateField: 'date',
12
12
  lapis: 'https://lapis.cov-spectrum.org/open/v2',
13
+ useNewEndpoint: false,
13
14
  },
14
15
  response: {
15
16
  overallMutationData: [
@@ -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> = (componentProps) => {
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<MutationOverTimeQuery, MutationOverTimeWorkerResponse>(
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 === null || data === undefined || data.overallMutationData.length === 0) {
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
- 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
  }