@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
@@ -0,0 +1,139 @@
1
+ import { type FunctionComponent, type h } from 'preact';
2
+ import { type Dispatch, type StateUpdater, useCallback, useEffect, useState } from 'preact/hooks';
3
+
4
+ import { Dropdown } from './dropdown';
5
+ import { useRawMutationAnnotations } from '../MutationAnnotationsContext';
6
+ import { type MutationFilter } from '../mutationsOverTime/getFilteredMutationsOverTimeData';
7
+ import { DeleteIcon } from '../shared/icons/DeleteIcon';
8
+
9
+ export type MutationsOverTimeMutationsFilterProps = {
10
+ setFilterValue: Dispatch<StateUpdater<MutationFilter>>;
11
+ value: MutationFilter;
12
+ };
13
+
14
+ export function MutationsOverTimeMutationsFilter({ setFilterValue, value }: MutationsOverTimeMutationsFilterProps) {
15
+ return (
16
+ <div className={'w-28 inline-flex'}>
17
+ <Dropdown buttonTitle={getButtonTitle(value)} placement={'bottom-start'}>
18
+ <TextInput value={value} setFilterValue={setFilterValue} />
19
+ <AnnotationCheckboxes value={value} setFilterValue={setFilterValue} />
20
+ </Dropdown>
21
+ </div>
22
+ );
23
+ }
24
+
25
+ function getButtonTitle(value: MutationFilter) {
26
+ if (value.textFilter === '' && value.annotationNameFilter.size === 0) {
27
+ return `Filter mutations`;
28
+ }
29
+
30
+ return [value.textFilter, ...value.annotationNameFilter].filter((it) => it !== '').join(', ');
31
+ }
32
+
33
+ const TextInput: FunctionComponent<MutationsOverTimeMutationsFilterProps> = ({ setFilterValue, value }) => {
34
+ const onInput = useCallback(
35
+ (newValue: string) => {
36
+ setFilterValue((previousFilter) => ({
37
+ ...previousFilter,
38
+ textFilter: newValue,
39
+ }));
40
+ },
41
+ [setFilterValue],
42
+ );
43
+
44
+ const onDeleteClick = () => {
45
+ setFilterValue((previousFilter) => ({
46
+ ...previousFilter,
47
+ textFilter: '',
48
+ }));
49
+ };
50
+
51
+ return (
52
+ <div>
53
+ <label className='flex gap-1 input input-xs'>
54
+ <DebouncedInput placeholder={'Filter'} onInput={onInput} value={value.textFilter} type='text' />
55
+ {value.textFilter !== '' && (
56
+ <button className={'cursor-pointer'} onClick={onDeleteClick}>
57
+ <DeleteIcon />
58
+ </button>
59
+ )}
60
+ </label>
61
+ </div>
62
+ );
63
+ };
64
+
65
+ function DebouncedInput({
66
+ value: initialValue,
67
+ onInput,
68
+ debounce = 500,
69
+ ...props
70
+ }: {
71
+ onInput: (value: string) => void;
72
+ debounce?: number;
73
+ value?: string;
74
+ } & Omit<h.JSX.IntrinsicElements['input'], 'onInput'>) {
75
+ const [value, setValue] = useState<string | undefined>(initialValue);
76
+
77
+ useEffect(() => {
78
+ setValue(initialValue);
79
+ }, [initialValue]);
80
+
81
+ useEffect(() => {
82
+ const timeout = setTimeout(() => {
83
+ onInput(value ?? '');
84
+ }, debounce);
85
+
86
+ return () => clearTimeout(timeout);
87
+ }, [value, debounce, onInput]);
88
+
89
+ const onChangeInput = useCallback((event: h.JSX.TargetedEvent<HTMLInputElement>) => {
90
+ setValue(event.currentTarget.value);
91
+ }, []);
92
+
93
+ return <input {...props} value={value} onInput={onChangeInput} />;
94
+ }
95
+
96
+ const AnnotationCheckboxes: FunctionComponent<MutationsOverTimeMutationsFilterProps> = ({ value, setFilterValue }) => {
97
+ const mutationAnnotations = useRawMutationAnnotations();
98
+
99
+ if (mutationAnnotations.length === 0) {
100
+ return null;
101
+ }
102
+
103
+ return (
104
+ <>
105
+ <div className='divider mt-0.5 mb-0' />
106
+ <div className='text-sm'>
107
+ <div className='font-bold mb-1'>Filter by annotations</div>
108
+ {mutationAnnotations.map((annotation, index) => (
109
+ <li className='flex flex-row items-center' key={annotation.name}>
110
+ <label>
111
+ <input
112
+ className={'mr-2'}
113
+ type='checkbox'
114
+ id={`item-${index}`}
115
+ checked={value.annotationNameFilter.has(annotation.name)}
116
+ onChange={() => {
117
+ setFilterValue((previousFilter) => {
118
+ const newAnnotationFilter = previousFilter.annotationNameFilter.has(
119
+ annotation.name,
120
+ )
121
+ ? [...previousFilter.annotationNameFilter].filter(
122
+ (name) => name !== annotation.name,
123
+ )
124
+ : [...previousFilter.annotationNameFilter, annotation.name];
125
+ return {
126
+ ...previousFilter,
127
+ annotationNameFilter: new Set(newAnnotationFilter),
128
+ };
129
+ });
130
+ }}
131
+ />
132
+ {annotation.name} (<span className='text-red-600'>{annotation.symbol}</span>)
133
+ </label>
134
+ </li>
135
+ ))}
136
+ </div>
137
+ </>
138
+ );
139
+ };
@@ -60,21 +60,21 @@ export const ProportionSelector: FunctionComponent<ProportionSelectorProps> = ({
60
60
  const indicateError = internalMinProportion > internalMaxProportion;
61
61
 
62
62
  return (
63
- <div class='flex flex-col w-64 mb-2'>
64
- <div class='flex items-center '>
63
+ <div className='flex flex-col w-64 mb-2'>
64
+ <div className='flex items-center '>
65
65
  <PercentInput
66
66
  percentage={internalMinProportion * 100}
67
67
  setPercentage={updateMinPercentage}
68
68
  indicateError={indicateError}
69
69
  />
70
- <div class='m-2'>-</div>
70
+ <div className='m-2'>-</div>
71
71
  <PercentInput
72
72
  percentage={internalMaxProportion * 100}
73
73
  setPercentage={updateMaxPercentage}
74
74
  indicateError={indicateError}
75
75
  />
76
76
  </div>
77
- <div class='my-1'>
77
+ <div className='my-1'>
78
78
  <MinMaxRangeSlider
79
79
  min={internalMinProportion * 100}
80
80
  max={internalMaxProportion * 100}
@@ -10,7 +10,7 @@ export interface SelectProps {
10
10
 
11
11
  export const Select: FunctionComponent<SelectProps> = ({ items, selected, onChange, selectStyle }) => {
12
12
  return (
13
- <select class={`select ${selectStyle} w-fit`} value={selected} onChange={onChange}>
13
+ <select className={`select ${selectStyle} w-fit`} value={selected} onChange={onChange}>
14
14
  {items.map((item) => (
15
15
  <option key={item.value} value={item.value} disabled={item.disabled}>
16
16
  {item.label}
@@ -29,7 +29,7 @@ export interface TableProps {
29
29
  export const Table = ({ data, columns, pageSize }: TableProps) => {
30
30
  const pagination = typeof pageSize === 'number' ? { limit: pageSize } : pageSize;
31
31
 
32
- const wrapper = useRef(null);
32
+ const wrapper = useRef<HTMLDivElement>(null);
33
33
 
34
34
  useEffect(() => {
35
35
  if (wrapper.current === null) {
@@ -40,7 +40,7 @@ const Tabs = forwardRef<HTMLDivElement, ComponentTabsProps>(({ tabs, toolbar },
40
40
  const toolbarElement = typeof toolbar === 'function' ? toolbar(activeTab) : toolbar;
41
41
 
42
42
  return (
43
- <div ref={ref} className='h-full w-full flex flex-col'>
43
+ <div ref={ref} className='h-full w-full flex flex-col bg-white'>
44
44
  <div className='flex flex-row justify-between flex-wrap'>
45
45
  {tabElements}
46
46
  {toolbar && <div className='py-2 flex flex-wrap gap-y-1'>{toolbarElement}</div>}
@@ -24,7 +24,7 @@ const tooltipContent = 'This is some content.';
24
24
 
25
25
  export const TooltipStory: StoryObj<TooltipProps> = {
26
26
  render: (args) => (
27
- <div class='flex justify-center px-4 py-16'>
27
+ <div className='flex justify-center px-4 py-16'>
28
28
  <Tooltip {...args}>
29
29
  <div className='bg-red-200'>Hover me</div>
30
30
  </Tooltip>
@@ -47,7 +47,7 @@ const Tooltip: FunctionComponent<TooltipProps> = ({ children, content, position
47
47
  <div>{children}</div>
48
48
  <div
49
49
  className={`absolute z-10 w-max bg-white p-4 border border-gray-200 rounded-md invisible group-hover:visible ${getPositionCss(position)}`}
50
- style={{ ...tooltipStyle }}
50
+ style={tooltipStyle}
51
51
  >
52
52
  {content}
53
53
  </div>
@@ -72,7 +72,7 @@ const XAxis: FunctionComponent<XAxisProps> = (componentProps) => {
72
72
  return (
73
73
  <div
74
74
  key={idx}
75
- class='absolute text-xs text-black px-1 hover:opacity-80 border-l border-r border-gray-400 border-t'
75
+ className='absolute text-xs text-black px-1 hover:opacity-80 border-l border-r border-gray-400 border-t'
76
76
  style={{
77
77
  left: `calc(${leftPercent}% - 1px)`,
78
78
  width: `calc(${widthPercent}% - 1px)`,
@@ -200,10 +200,10 @@ const CDSPlot: FunctionComponent<CDSProps> = (componentProps) => {
200
200
  };
201
201
 
202
202
  return (
203
- <div ref={ref} class='p-4'>
203
+ <div ref={ref} className='p-4'>
204
204
  <CDSBars gffData={gffData} zoomStart={zoomStart} zoomEnd={zoomEnd} />
205
205
  <XAxis zoomStart={zoomStart} zoomEnd={zoomEnd} fullWidth={width} />
206
- <div class='relative w-full h-5'>
206
+ <div className='relative w-full h-5'>
207
207
  <MinMaxRangeSlider
208
208
  min={zoomStart}
209
209
  max={zoomEnd}
@@ -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
 
@@ -1,7 +1,7 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
 
3
3
  import { BaseMutationOverTimeDataMap } from './MutationOverTimeData';
4
- import { getFilteredMutationOverTimeData } from './getFilteredMutationsOverTimeData';
4
+ import { getFilteredMutationOverTimeData, type MutationFilter } from './getFilteredMutationsOverTimeData';
5
5
  import { type MutationOverTimeMutationValue } from '../../query/queryMutationsOverTime';
6
6
  import { type DeletionEntry, type SubstitutionEntry } from '../../types';
7
7
  import { type Deletion, type Substitution } from '../../utils/mutations';
@@ -28,7 +28,7 @@ describe('getFilteredMutationOverTimeData', () => {
28
28
  displayedMutationTypes: [],
29
29
  proportionInterval,
30
30
  displayMutations: undefined,
31
- mutationFilterValue: '',
31
+ mutationFilterValue: { textFilter: '', annotationNameFilter: new Set() },
32
32
  sequenceType: 'nucleotide',
33
33
  annotationProvider: () => {
34
34
  return [];
@@ -62,7 +62,7 @@ describe('getFilteredMutationOverTimeData', () => {
62
62
  },
63
63
  ],
64
64
  proportionInterval,
65
- mutationFilterValue: '',
65
+ mutationFilterValue: { textFilter: '', annotationNameFilter: new Set() },
66
66
  sequenceType: 'nucleotide',
67
67
  annotationProvider: () => {
68
68
  return [];
@@ -85,7 +85,7 @@ describe('getFilteredMutationOverTimeData', () => {
85
85
  displayedSegments: [],
86
86
  displayedMutationTypes: [],
87
87
  proportionInterval,
88
- mutationFilterValue: '',
88
+ mutationFilterValue: { textFilter: '', annotationNameFilter: new Set() },
89
89
  sequenceType: 'nucleotide',
90
90
  annotationProvider: () => {
91
91
  return [];
@@ -108,7 +108,7 @@ describe('getFilteredMutationOverTimeData', () => {
108
108
  displayedSegments: [],
109
109
  displayedMutationTypes: [],
110
110
  proportionInterval,
111
- mutationFilterValue: '',
111
+ mutationFilterValue: { textFilter: '', annotationNameFilter: new Set() },
112
112
  sequenceType: 'nucleotide',
113
113
  annotationProvider: () => {
114
114
  return [];
@@ -132,7 +132,7 @@ describe('getFilteredMutationOverTimeData', () => {
132
132
  displayedSegments: [],
133
133
  displayedMutationTypes: [],
134
134
  proportionInterval,
135
- mutationFilterValue: '',
135
+ mutationFilterValue: { textFilter: '', annotationNameFilter: new Set() },
136
136
  sequenceType: 'nucleotide',
137
137
  annotationProvider: () => {
138
138
  return [];
@@ -156,7 +156,7 @@ describe('getFilteredMutationOverTimeData', () => {
156
156
  displayedSegments: [],
157
157
  displayedMutationTypes: [],
158
158
  proportionInterval,
159
- mutationFilterValue: '',
159
+ mutationFilterValue: { textFilter: '', annotationNameFilter: new Set() },
160
160
  sequenceType: 'nucleotide',
161
161
  annotationProvider: () => {
162
162
  return [];
@@ -178,7 +178,7 @@ describe('getFilteredMutationOverTimeData', () => {
178
178
  displayedSegments: [],
179
179
  displayedMutationTypes: [],
180
180
  proportionInterval,
181
- mutationFilterValue: '',
181
+ mutationFilterValue: { textFilter: '', annotationNameFilter: new Set() },
182
182
  sequenceType: 'nucleotide',
183
183
  annotationProvider: () => {
184
184
  return [];
@@ -201,7 +201,7 @@ describe('getFilteredMutationOverTimeData', () => {
201
201
  displayedSegments: [],
202
202
  displayedMutationTypes: [],
203
203
  proportionInterval,
204
- mutationFilterValue: '',
204
+ mutationFilterValue: { textFilter: '', annotationNameFilter: new Set() },
205
205
  sequenceType: 'nucleotide',
206
206
  annotationProvider: () => {
207
207
  return [];
@@ -225,7 +225,7 @@ describe('getFilteredMutationOverTimeData', () => {
225
225
  displayedMutationTypes: [],
226
226
  proportionInterval,
227
227
  displayMutations: [anotherSubstitution.code, someDeletion.code],
228
- mutationFilterValue: '',
228
+ mutationFilterValue: { textFilter: '', annotationNameFilter: new Set() },
229
229
  sequenceType: 'nucleotide',
230
230
  annotationProvider: () => {
231
231
  return [];
@@ -235,7 +235,7 @@ describe('getFilteredMutationOverTimeData', () => {
235
235
  expect(result.getFirstAxisKeys()).to.deep.equal([anotherSubstitution, someDeletion]);
236
236
  });
237
237
 
238
- it('should filter by mutation filter value', () => {
238
+ it('should filter by mutation filter text value', () => {
239
239
  const { data, overallMutationData } = prepareMutationOverTimeData([
240
240
  someSubstitutionEntry,
241
241
  anotherSubstitutionEntry,
@@ -248,7 +248,7 @@ describe('getFilteredMutationOverTimeData', () => {
248
248
  displayedSegments: [],
249
249
  displayedMutationTypes: [],
250
250
  proportionInterval,
251
- mutationFilterValue: '23T',
251
+ mutationFilterValue: { textFilter: '23T', annotationNameFilter: new Set() },
252
252
  sequenceType: 'nucleotide',
253
253
  annotationProvider: () => {
254
254
  return [];
@@ -265,7 +265,7 @@ describe('getFilteredMutationOverTimeData', () => {
265
265
  someDeletionEntry,
266
266
  ]);
267
267
 
268
- const expectFilteredValue = (filterValue: string, annotations: MutationAnnotations) => {
268
+ const expectFilteredValue = (filterValue: MutationFilter, annotations: MutationAnnotations) => {
269
269
  const annotationProvider = getMutationAnnotationsProvider(getMutationAnnotationsContext(annotations));
270
270
 
271
271
  const result = getFilteredMutationOverTimeData({
@@ -283,7 +283,7 @@ describe('getFilteredMutationOverTimeData', () => {
283
283
  };
284
284
 
285
285
  it('with filter value in symbol', () => {
286
- expectFilteredValue('#', [
286
+ expectFilteredValue({ textFilter: '#', annotationNameFilter: new Set() }, [
287
287
  {
288
288
  name: 'Annotation 1',
289
289
  description: 'Description 1',
@@ -294,7 +294,7 @@ describe('getFilteredMutationOverTimeData', () => {
294
294
  });
295
295
 
296
296
  it('with filter value in name', () => {
297
- expectFilteredValue('Annota', [
297
+ expectFilteredValue({ textFilter: 'Annota', annotationNameFilter: new Set() }, [
298
298
  {
299
299
  name: 'Annotation 1 #',
300
300
  description: 'Description 1',
@@ -305,7 +305,18 @@ describe('getFilteredMutationOverTimeData', () => {
305
305
  });
306
306
 
307
307
  it('with filter value in name', () => {
308
- expectFilteredValue('Descr', [
308
+ expectFilteredValue({ textFilter: 'Descr', annotationNameFilter: new Set() }, [
309
+ {
310
+ name: 'Annotation 1',
311
+ description: 'Description 1',
312
+ symbol: '#',
313
+ nucleotideMutations: ['A123T'],
314
+ },
315
+ ]);
316
+ });
317
+
318
+ it('with annotation name filter', () => {
319
+ expectFilteredValue({ textFilter: '', annotationNameFilter: new Set(['Annotation 1']) }, [
309
320
  {
310
321
  name: 'Annotation 1',
311
322
  description: 'Description 1',