@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.
- package/custom-elements.json +2 -2
- package/dist/{NumberRangeFilterChangedEvent-B64OQZjX.js → NumberRangeFilterChangedEvent-CQ32Qy8D.js} +2 -2
- package/dist/NumberRangeFilterChangedEvent-CQ32Qy8D.js.map +1 -0
- package/dist/assets/mutationOverTimeWorker-DpW4YOGl.js.map +1 -0
- package/dist/components.d.ts +29 -29
- package/dist/components.js +186 -145
- package/dist/components.js.map +1 -1
- package/dist/util.d.ts +29 -29
- package/dist/util.js +1 -1
- package/package.json +7 -3
- package/src/lapisApi/lapisApi.ts +2 -2
- package/src/operator/DivisionOperator.ts +4 -2
- package/src/operator/FetchDetailsOperator.ts +1 -1
- package/src/operator/RenameFieldOperator.ts +3 -3
- package/src/preact/MutationAnnotationsContext.tsx +15 -7
- package/src/preact/aggregatedData/aggregate.tsx +0 -5
- package/src/preact/components/annotated-mutation.tsx +0 -1
- package/src/preact/components/clearable-select.stories.tsx +1 -1
- package/src/preact/components/confidence-interval-selector.tsx +1 -1
- package/src/preact/components/error-boundary.tsx +1 -5
- package/src/preact/components/error-display.tsx +1 -1
- package/src/preact/components/fullscreen.tsx +2 -5
- package/src/preact/components/info.stories.tsx +1 -1
- package/src/preact/components/min-max-range-slider.tsx +1 -1
- package/src/preact/components/mutations-over-time-mutations-filter.stories.tsx +109 -0
- package/src/preact/components/mutations-over-time-mutations-filter.tsx +139 -0
- package/src/preact/components/proportion-selector.tsx +4 -4
- package/src/preact/components/select.tsx +1 -1
- package/src/preact/components/table.tsx +1 -1
- package/src/preact/components/tabs.tsx +1 -1
- package/src/preact/components/tooltip.stories.tsx +1 -1
- package/src/preact/components/tooltip.tsx +1 -1
- package/src/preact/genomeViewer/CDSPlot.tsx +3 -3
- package/src/preact/genomeViewer/loadGff3.ts +5 -8
- package/src/preact/lineageFilter/lineage-filter.tsx +1 -1
- package/src/preact/locationFilter/location-filter.tsx +4 -4
- package/src/preact/mutationComparison/getMutationComparisonTableData.ts +1 -3
- package/src/preact/mutationComparison/mutation-comparison-venn.tsx +1 -1
- package/src/preact/mutationComparison/mutation-comparison.tsx +0 -5
- package/src/preact/mutationFilter/mutation-filter-info.tsx +2 -2
- package/src/preact/mutationFilter/mutation-filter.tsx +1 -1
- package/src/preact/mutations/getMutationsGridData.ts +2 -6
- package/src/preact/mutations/getMutationsTableData.ts +1 -1
- package/src/preact/mutations/mutations-grid.tsx +1 -1
- package/src/preact/mutations/mutations.tsx +0 -5
- package/src/preact/mutationsOverTime/getFilteredMutationsOverTime.spec.ts +27 -16
- package/src/preact/mutationsOverTime/getFilteredMutationsOverTimeData.ts +45 -11
- package/src/preact/mutationsOverTime/mutations-over-time.tsx +16 -15
- package/src/preact/numberRangeFilter/number-range-filter.tsx +4 -4
- package/src/preact/numberSequencesOverTime/getNumberOfSequencesOverTimeTableData.ts +1 -4
- package/src/preact/numberSequencesOverTime/number-sequences-over-time.tsx +0 -5
- package/src/preact/prevalenceOverTime/prevalence-over-time-bar-chart.tsx +1 -1
- package/src/preact/prevalenceOverTime/prevalence-over-time-bubble-chart.tsx +1 -1
- package/src/preact/prevalenceOverTime/prevalence-over-time-line-chart.tsx +1 -1
- package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +1 -1
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage-chart.tsx +5 -5
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +1 -4
- package/src/preact/sequencesByLocation/loadMapSource.tsx +5 -2
- package/src/preact/shared/aspectRatio/AspectRatio.tsx +1 -1
- package/src/preact/shared/floating-ui/hooks.ts +2 -2
- package/src/preact/shared/sort/sortMutationPositions.ts +2 -2
- package/src/preact/shared/tanstackTable/pagination.tsx +2 -2
- package/src/preact/shared/tanstackTable/tanstackTable.tsx +1 -1
- package/src/preact/statistic/statistics.tsx +0 -5
- package/src/preact/textFilter/fetchStringAutocompleteList.ts +1 -10
- package/src/preact/textFilter/text-filter.tsx +1 -6
- package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.tsx +14 -8
- package/src/preact/webWorkers/useWebWorker.ts +2 -1
- package/src/preact/webWorkers/workerFunction.ts +2 -2
- package/src/query/computeMapLocationData.ts +1 -1
- package/src/query/queryAggregatedDataOverTime.ts +3 -3
- package/src/query/queryMutationsOverTime.spec.ts +9 -9
- package/src/query/queryMutationsOverTime.ts +22 -16
- package/src/query/queryRelativeGrowthAdvantage.ts +5 -9
- package/src/query/queryWastewaterMutationsOverTime.ts +1 -1
- package/src/types.ts +1 -1
- package/src/utils/mutations.ts +10 -10
- package/src/utils/type-utils.ts +1 -1
- package/src/utils/typeAssertions.spec.ts +1 -1
- package/src/web-components/gs-app.spec-d.ts +1 -1
- package/src/web-components/gs-app.stories.ts +1 -1
- package/src/web-components/input/gs-date-range-filter.tsx +2 -2
- package/src/web-components/input/gs-lineage-filter.tsx +2 -2
- package/src/web-components/input/gs-location-filter.tsx +3 -3
- package/src/web-components/input/gs-mutation-filter.tsx +2 -2
- package/src/web-components/input/gs-number-range-filter.spec.ts +1 -1
- package/src/web-components/input/gs-text-filter.tsx +2 -2
- package/src/web-components/visualization/gs-aggregate.tsx +2 -2
- package/src/web-components/visualization/gs-genome-data-viewer.spec-d.ts +1 -1
- package/src/web-components/visualization/gs-mutation-comparison.tsx +2 -2
- package/src/web-components/visualization/gs-mutations.tsx +2 -2
- package/src/web-components/visualization/gs-number-sequences-over-time.tsx +2 -2
- package/src/web-components/visualization/gs-prevalence-over-time.tsx +2 -2
- package/src/web-components/visualization/gs-relative-growth-advantage.tsx +2 -2
- package/src/web-components/visualization/gs-sequences-by-location.tsx +2 -2
- package/src/web-components/visualization/gs-statistics.tsx +2 -2
- package/standalone-bundle/assets/mutationOverTimeWorker-CZVvQBze.js.map +1 -0
- package/standalone-bundle/dashboard-components.js +3989 -3923
- package/standalone-bundle/dashboard-components.js.map +1 -1
- package/dist/NumberRangeFilterChangedEvent-B64OQZjX.js.map +0 -1
- package/dist/assets/mutationOverTimeWorker-DjH04AQB.js.map +0 -1
- package/src/preact/components/mutations-over-time-text-filter.stories.tsx +0 -57
- package/src/preact/components/mutations-over-time-text-filter.tsx +0 -63
- 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
|
|
64
|
-
<div
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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={
|
|
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
|
-
|
|
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}
|
|
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
|
|
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
|
-
|
|
29
|
-
genomeLength = loadGenomeLength(content);
|
|
30
|
-
}
|
|
26
|
+
genomeLength ??= loadGenomeLength(content);
|
|
31
27
|
return { features: parseGFF3(content), length: genomeLength };
|
|
32
28
|
}
|
|
33
29
|
|
|
@@ -89,7 +85,7 @@ function getCDSMap(lines: string[], genome_type: string, geneMap: CDSMap): CDSMa
|
|
|
89
85
|
}
|
|
90
86
|
|
|
91
87
|
const attrPairs = getAttributes(attributes);
|
|
92
|
-
const labelAttribute = attrPairs.get('Name')
|
|
88
|
+
const labelAttribute = attrPairs.get('Name') ?? attrPairs.get('gene') ?? attrPairs.get('gene_name');
|
|
93
89
|
if (!labelAttribute) {
|
|
94
90
|
throw new UserFacingError(
|
|
95
91
|
'Invalid gff3 source',
|
|
@@ -97,11 +93,12 @@ function getCDSMap(lines: string[], genome_type: string, geneMap: CDSMap): CDSMa
|
|
|
97
93
|
);
|
|
98
94
|
}
|
|
99
95
|
const label = removeQuotes(labelAttribute);
|
|
100
|
-
const id = removeQuotes(attrPairs.get('ID')
|
|
96
|
+
const id = removeQuotes(attrPairs.get('ID') ?? labelAttribute);
|
|
101
97
|
const parentAttribute = attrPairs.get('Parent');
|
|
102
98
|
if (parentAttribute) {
|
|
103
99
|
const parent = removeQuotes(parentAttribute);
|
|
104
100
|
if (parent && parent in geneMap) {
|
|
101
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
105
102
|
delete geneMap[parent];
|
|
106
103
|
}
|
|
107
104
|
}
|
|
@@ -99,5 +99,5 @@ function filterByInputValue(item: LineageItem, inputValue: string | null) {
|
|
|
99
99
|
if (inputValue === null || inputValue === '') {
|
|
100
100
|
return true;
|
|
101
101
|
}
|
|
102
|
-
return item.lineage
|
|
102
|
+
return item.lineage.toLowerCase().includes(inputValue.toLowerCase() || '');
|
|
103
103
|
}
|
|
@@ -110,8 +110,8 @@ function filterByInputValue(item: SelectItem, inputValue: string | null) {
|
|
|
110
110
|
return true;
|
|
111
111
|
}
|
|
112
112
|
return (
|
|
113
|
-
item
|
|
114
|
-
item
|
|
113
|
+
!!item.label?.toLowerCase().includes(inputValue.toLowerCase()) ||
|
|
114
|
+
item.description.toLowerCase().includes(inputValue.toLowerCase())
|
|
115
115
|
);
|
|
116
116
|
}
|
|
117
117
|
|
|
@@ -143,8 +143,8 @@ function concatenateLocation(locationFilter: LapisLocationFilter, fields: string
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
function emptyLocationFilter(fields: string[]) {
|
|
146
|
-
return fields.reduce((acc, field) => {
|
|
146
|
+
return fields.reduce<LapisLocationFilter>((acc, field) => {
|
|
147
147
|
acc[field] = undefined;
|
|
148
148
|
return acc;
|
|
149
|
-
}, {}
|
|
149
|
+
}, {});
|
|
150
150
|
}
|
|
@@ -3,9 +3,7 @@ import { type Dataset } from '../../operator/Dataset';
|
|
|
3
3
|
import { type DeletionClass, type SubstitutionClass } from '../../utils/mutations';
|
|
4
4
|
import { type ProportionInterval } from '../components/proportion-selector';
|
|
5
5
|
|
|
6
|
-
type Proportions =
|
|
7
|
-
[displayName: string]: number;
|
|
8
|
-
};
|
|
6
|
+
type Proportions = Record<string, number>;
|
|
9
7
|
|
|
10
8
|
type MutationComparisonRow = {
|
|
11
9
|
mutation: SubstitutionClass | DeletionClass;
|
|
@@ -110,7 +110,7 @@ export const MutationComparisonVenn: FunctionComponent<MutationComparisonVennPro
|
|
|
110
110
|
<div className='flex-1'>
|
|
111
111
|
<GsChart configuration={config} />
|
|
112
112
|
</div>
|
|
113
|
-
<p
|
|
113
|
+
<p className='flex flex-wrap break-words m-2'>
|
|
114
114
|
<SelectedMutationsDescription
|
|
115
115
|
selectedDatasetIndex={selectedDatasetIndex}
|
|
116
116
|
sets={sets}
|
|
@@ -16,7 +16,6 @@ import Info, { InfoComponentCode, InfoHeadline1, InfoHeadline2, InfoParagraph }
|
|
|
16
16
|
import { LoadingDisplay } from '../components/loading-display';
|
|
17
17
|
import { DeletionsLink, ProportionExplanation, SubstitutionsLink } from '../components/mutation-info';
|
|
18
18
|
import { type DisplayedMutationType, MutationTypeSelector } from '../components/mutation-type-selector';
|
|
19
|
-
import { NoDataDisplay } from '../components/no-data-display';
|
|
20
19
|
import { type ProportionInterval } from '../components/proportion-selector';
|
|
21
20
|
import { ProportionSelectorDropdown } from '../components/proportion-selector-dropdown';
|
|
22
21
|
import { ResizeContainer } from '../components/resize-container';
|
|
@@ -68,10 +67,6 @@ const MutationComparisonInner: FunctionComponent<MutationComparisonProps> = (com
|
|
|
68
67
|
throw error;
|
|
69
68
|
}
|
|
70
69
|
|
|
71
|
-
if (data === null) {
|
|
72
|
-
return <NoDataDisplay />;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
70
|
return <MutationComparisonTabs data={data.mutationData} originalComponentProps={componentProps} />;
|
|
76
71
|
};
|
|
77
72
|
|
|
@@ -191,10 +191,10 @@ const exampleSegmentString = (referenceGenome: ReferenceGenome) => {
|
|
|
191
191
|
|
|
192
192
|
const exampleSegment = (referenceGenome: ReferenceGenome) => {
|
|
193
193
|
if (referenceGenome.genes.length > 0) {
|
|
194
|
-
return
|
|
194
|
+
return referenceGenome.genes[0].name;
|
|
195
195
|
}
|
|
196
196
|
if (referenceGenome.nucleotideSequences.length > 1) {
|
|
197
|
-
return
|
|
197
|
+
return referenceGenome.nucleotideSequences[0].name;
|
|
198
198
|
}
|
|
199
199
|
return '';
|
|
200
200
|
};
|
|
@@ -112,7 +112,7 @@ function MutationFilterInner({ initialValue }: MutationFilterInnerProps) {
|
|
|
112
112
|
const handleInputChange = (newInputValue: string | undefined) => {
|
|
113
113
|
setShowErrorIndicator(false);
|
|
114
114
|
if (newInputValue?.includes(',')) {
|
|
115
|
-
const values = newInputValue
|
|
115
|
+
const values = newInputValue.split(',').map((value) => {
|
|
116
116
|
return { value, parsedValue: parseAndValidateMutation(value.trim(), referenceGenome) };
|
|
117
117
|
});
|
|
118
118
|
const validEntries = values.map((value) => value.parsedValue).filter((value) => value !== null);
|
|
@@ -18,9 +18,7 @@ const accumulateByPosition = (data: SubstitutionOrDeletionEntry[], sequenceType:
|
|
|
18
18
|
const referenceBases = new Map<string, string | undefined>();
|
|
19
19
|
|
|
20
20
|
for (const mutationEntry of data) {
|
|
21
|
-
const position =
|
|
22
|
-
(mutationEntry.mutation.segment ? `${mutationEntry.mutation.segment}:` : '') +
|
|
23
|
-
mutationEntry.mutation.position;
|
|
21
|
+
const position = `${mutationEntry.mutation.segment ? `${mutationEntry.mutation.segment}:` : ''}${mutationEntry.mutation.position}`;
|
|
24
22
|
referenceBases.set(position, mutationEntry.mutation.valueAtReference);
|
|
25
23
|
|
|
26
24
|
const initiallyFillPositionsToProportionAtBase = () => {
|
|
@@ -69,9 +67,7 @@ const accumulateByPosition = (data: SubstitutionOrDeletionEntry[], sequenceType:
|
|
|
69
67
|
});
|
|
70
68
|
};
|
|
71
69
|
|
|
72
|
-
export type BasesData =
|
|
73
|
-
[base: string]: BaseCell;
|
|
74
|
-
};
|
|
70
|
+
export type BasesData = Record<string, BaseCell>;
|
|
75
71
|
export type MutationsGridDataRow = BasesData & { position: string };
|
|
76
72
|
|
|
77
73
|
const byProportion = (row: MutationsGridDataRow, proportionInterval: ProportionInterval) => {
|
|
@@ -30,7 +30,7 @@ export function getMutationsTableData(
|
|
|
30
30
|
}, new Map<string, number>());
|
|
31
31
|
|
|
32
32
|
return tableData.map((datum) => {
|
|
33
|
-
const baselineMutationCount = baselineMutationCounts.get(datum.mutation.code)
|
|
33
|
+
const baselineMutationCount = baselineMutationCounts.get(datum.mutation.code) ?? 0;
|
|
34
34
|
const jaccardSimilarity = calculateJaccardSimilarity(overallVariantCount, baselineMutationCount, datum.count);
|
|
35
35
|
|
|
36
36
|
return {
|
|
@@ -62,7 +62,7 @@ export const MutationsGrid: FunctionComponent<MutationsGridProps> = ({
|
|
|
62
62
|
},
|
|
63
63
|
formatter: (cell: BaseCell) => formatProportion(cell.proportion),
|
|
64
64
|
attributes: (cell: BaseCell, row: Row) => {
|
|
65
|
-
// grid-js: the cell and row are null for header cells
|
|
65
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- grid-js: the cell and row are null for header cells
|
|
66
66
|
if (row === null) {
|
|
67
67
|
return {};
|
|
68
68
|
}
|
|
@@ -24,7 +24,6 @@ import Info, { InfoComponentCode, InfoHeadline1, InfoParagraph } from '../compon
|
|
|
24
24
|
import { LoadingDisplay } from '../components/loading-display';
|
|
25
25
|
import { DeletionsLink, InsertionsLink, ProportionExplanation, SubstitutionsLink } from '../components/mutation-info';
|
|
26
26
|
import { type DisplayedMutationType, MutationTypeSelector } from '../components/mutation-type-selector';
|
|
27
|
-
import { NoDataDisplay } from '../components/no-data-display';
|
|
28
27
|
import type { ProportionInterval } from '../components/proportion-selector';
|
|
29
28
|
import { ProportionSelectorDropdown } from '../components/proportion-selector-dropdown';
|
|
30
29
|
import { ResizeContainer } from '../components/resize-container';
|
|
@@ -75,10 +74,6 @@ export const MutationsInner: FunctionComponent<MutationsProps> = (componentProps
|
|
|
75
74
|
throw error;
|
|
76
75
|
}
|
|
77
76
|
|
|
78
|
-
if (data === null) {
|
|
79
|
-
return <NoDataDisplay />;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
77
|
return <MutationsTabs mutationsData={data} originalComponentProps={componentProps} />;
|
|
83
78
|
};
|
|
84
79
|
|
|
@@ -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:
|
|
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',
|