@genspectrum/dashboard-components 0.4.3 → 0.4.5
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 +145 -12
- package/dist/dashboard-components.js +232 -149
- package/dist/dashboard-components.js.map +1 -1
- package/dist/genspectrum-components.d.ts +33 -0
- package/package.json +1 -1
- package/src/preact/aggregatedData/aggregate-table.tsx +3 -2
- package/src/preact/aggregatedData/aggregate.stories.tsx +6 -0
- package/src/preact/aggregatedData/aggregate.tsx +28 -6
- package/src/preact/components/table.stories.tsx +51 -1
- package/src/preact/components/table.tsx +4 -3
- package/src/preact/locationFilter/location-filter.stories.tsx +12 -1
- package/src/preact/locationFilter/location-filter.tsx +10 -3
- package/src/preact/mutationComparison/mutation-comparison-table.tsx +7 -2
- package/src/preact/mutationComparison/mutation-comparison.stories.tsx +3 -0
- package/src/preact/mutationComparison/mutation-comparison.tsx +25 -3
- package/src/preact/mutations/mutations-grid.tsx +8 -2
- package/src/preact/mutations/mutations-insertions-table.tsx +3 -2
- package/src/preact/mutations/mutations-table.tsx +3 -2
- package/src/preact/mutations/mutations.stories.tsx +3 -0
- package/src/preact/mutations/mutations.tsx +16 -6
- package/src/preact/prevalenceOverTime/prevalence-over-time-table.tsx +3 -2
- package/src/preact/prevalenceOverTime/prevalence-over-time.stories.tsx +4 -0
- package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +8 -1
- package/src/query/queryAggregateData.spec.ts +117 -3
- package/src/query/queryAggregateData.ts +31 -2
- package/src/web-components/input/gs-location-filter.stories.ts +11 -0
- package/src/web-components/input/gs-location-filter.tsx +14 -1
- package/src/web-components/visualization/gs-aggregate.stories.ts +15 -0
- package/src/web-components/visualization/gs-aggregate.tsx +23 -0
- package/src/web-components/visualization/gs-mutation-comparison.stories.ts +4 -0
- package/src/web-components/visualization/gs-mutation-comparison.tsx +8 -0
- package/src/web-components/visualization/gs-mutations.stories.ts +4 -0
- package/src/web-components/visualization/gs-mutations.tsx +8 -0
- package/src/web-components/visualization/gs-prevalence-over-time.stories.ts +5 -0
- package/src/web-components/visualization/gs-prevalence-over-time.tsx +12 -4
|
@@ -35,6 +35,7 @@ export interface MutationsInnerProps {
|
|
|
35
35
|
variant: LapisFilter;
|
|
36
36
|
sequenceType: SequenceType;
|
|
37
37
|
views: View[];
|
|
38
|
+
pageSize: boolean | number;
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
export interface MutationsProps extends MutationsInnerProps {
|
|
@@ -50,6 +51,7 @@ export const Mutations: FunctionComponent<MutationsProps> = ({
|
|
|
50
51
|
width,
|
|
51
52
|
height,
|
|
52
53
|
headline = 'Mutations',
|
|
54
|
+
pageSize,
|
|
53
55
|
}) => {
|
|
54
56
|
const size = { height, width };
|
|
55
57
|
|
|
@@ -57,14 +59,14 @@ export const Mutations: FunctionComponent<MutationsProps> = ({
|
|
|
57
59
|
<ErrorBoundary size={size} headline={headline}>
|
|
58
60
|
<ResizeContainer size={size}>
|
|
59
61
|
<Headline heading={headline}>
|
|
60
|
-
<MutationsInner variant={variant} sequenceType={sequenceType} views={views} />
|
|
62
|
+
<MutationsInner variant={variant} sequenceType={sequenceType} views={views} pageSize={pageSize} />
|
|
61
63
|
</Headline>
|
|
62
64
|
</ResizeContainer>
|
|
63
65
|
</ErrorBoundary>
|
|
64
66
|
);
|
|
65
67
|
};
|
|
66
68
|
|
|
67
|
-
export const MutationsInner: FunctionComponent<MutationsInnerProps> = ({ variant, sequenceType, views }) => {
|
|
69
|
+
export const MutationsInner: FunctionComponent<MutationsInnerProps> = ({ variant, sequenceType, views, pageSize }) => {
|
|
68
70
|
const lapis = useContext(LapisUrlContext);
|
|
69
71
|
const { data, error, isLoading } = useQuery(async () => {
|
|
70
72
|
return queryMutationsData(variant, sequenceType, lapis);
|
|
@@ -82,16 +84,17 @@ export const MutationsInner: FunctionComponent<MutationsInnerProps> = ({ variant
|
|
|
82
84
|
return <NoDataDisplay />;
|
|
83
85
|
}
|
|
84
86
|
|
|
85
|
-
return <MutationsTabs mutationsData={data} sequenceType={sequenceType} views={views} />;
|
|
87
|
+
return <MutationsTabs mutationsData={data} sequenceType={sequenceType} views={views} pageSize={pageSize} />;
|
|
86
88
|
};
|
|
87
89
|
|
|
88
90
|
type MutationTabsProps = {
|
|
89
91
|
mutationsData: { insertions: InsertionEntry[]; substitutionsOrDeletions: SubstitutionOrDeletionEntry[] };
|
|
90
92
|
sequenceType: SequenceType;
|
|
91
93
|
views: View[];
|
|
94
|
+
pageSize: boolean | number;
|
|
92
95
|
};
|
|
93
96
|
|
|
94
|
-
const MutationsTabs: FunctionComponent<MutationTabsProps> = ({ mutationsData, sequenceType, views }) => {
|
|
97
|
+
const MutationsTabs: FunctionComponent<MutationTabsProps> = ({ mutationsData, sequenceType, views, pageSize }) => {
|
|
95
98
|
const [proportionInterval, setProportionInterval] = useState({ min: 0.05, max: 1 });
|
|
96
99
|
|
|
97
100
|
const [displayedSegments, setDisplayedSegments] = useDisplayedSegments(sequenceType);
|
|
@@ -107,7 +110,13 @@ const MutationsTabs: FunctionComponent<MutationTabsProps> = ({ mutationsData, se
|
|
|
107
110
|
case 'table':
|
|
108
111
|
return {
|
|
109
112
|
title: 'Table',
|
|
110
|
-
content:
|
|
113
|
+
content: (
|
|
114
|
+
<MutationsTable
|
|
115
|
+
data={filteredData.tableData}
|
|
116
|
+
proportionInterval={proportionInterval}
|
|
117
|
+
pageSize={pageSize}
|
|
118
|
+
/>
|
|
119
|
+
),
|
|
111
120
|
};
|
|
112
121
|
case 'grid':
|
|
113
122
|
return {
|
|
@@ -117,13 +126,14 @@ const MutationsTabs: FunctionComponent<MutationTabsProps> = ({ mutationsData, se
|
|
|
117
126
|
data={filteredData.gridData}
|
|
118
127
|
sequenceType={sequenceType}
|
|
119
128
|
proportionInterval={proportionInterval}
|
|
129
|
+
pageSize={pageSize}
|
|
120
130
|
/>
|
|
121
131
|
),
|
|
122
132
|
};
|
|
123
133
|
case 'insertions':
|
|
124
134
|
return {
|
|
125
135
|
title: 'Insertions',
|
|
126
|
-
content: <InsertionsTable data={filteredData.insertions} />,
|
|
136
|
+
content: <InsertionsTable data={filteredData.insertions} pageSize={pageSize} />,
|
|
127
137
|
};
|
|
128
138
|
}
|
|
129
139
|
};
|
|
@@ -7,9 +7,10 @@ import { formatProportion } from '../shared/table/formatProportion';
|
|
|
7
7
|
interface PrevalenceOverTimeTableProps {
|
|
8
8
|
data: PrevalenceOverTimeData;
|
|
9
9
|
granularity: TemporalGranularity;
|
|
10
|
+
pageSize: boolean | number;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
const PrevalenceOverTimeTable = ({ data, granularity }: PrevalenceOverTimeTableProps) => {
|
|
13
|
+
const PrevalenceOverTimeTable = ({ data, granularity, pageSize }: PrevalenceOverTimeTableProps) => {
|
|
13
14
|
const getSplitColumns = (data: PrevalenceOverTimeData) => {
|
|
14
15
|
return data.map((dataset) => ({
|
|
15
16
|
name: dataset.displayName,
|
|
@@ -40,7 +41,7 @@ const PrevalenceOverTimeTable = ({ data, granularity }: PrevalenceOverTimeTableP
|
|
|
40
41
|
return Object.values(dataByHeader).map((row) => Object.values(row));
|
|
41
42
|
};
|
|
42
43
|
|
|
43
|
-
return <Table data={getData(data, granularity)} columns={getColumns(data)}
|
|
44
|
+
return <Table data={getData(data, granularity)} columns={getColumns(data)} pageSize={pageSize} />;
|
|
44
45
|
};
|
|
45
46
|
|
|
46
47
|
export default PrevalenceOverTimeTable;
|
|
@@ -32,6 +32,7 @@ export default {
|
|
|
32
32
|
width: { control: 'text' },
|
|
33
33
|
height: { control: 'text' },
|
|
34
34
|
headline: { control: 'text' },
|
|
35
|
+
pageSize: { control: 'object' },
|
|
35
36
|
},
|
|
36
37
|
};
|
|
37
38
|
|
|
@@ -49,6 +50,7 @@ const Template = {
|
|
|
49
50
|
height={args.height}
|
|
50
51
|
headline={args.headline}
|
|
51
52
|
lapisDateField={args.lapisDateField}
|
|
53
|
+
pageSize={args.pageSize}
|
|
52
54
|
/>
|
|
53
55
|
</LapisUrlContext.Provider>
|
|
54
56
|
),
|
|
@@ -70,6 +72,7 @@ export const TwoVariants = {
|
|
|
70
72
|
height: '700px',
|
|
71
73
|
headline: 'Prevalence over time',
|
|
72
74
|
lapisDateField: 'date',
|
|
75
|
+
pageSize: 10,
|
|
73
76
|
},
|
|
74
77
|
parameters: {
|
|
75
78
|
fetchMock: {
|
|
@@ -142,6 +145,7 @@ export const OneVariant = {
|
|
|
142
145
|
height: '700px',
|
|
143
146
|
headline: 'Prevalence over time',
|
|
144
147
|
lapisDateField: 'date',
|
|
148
|
+
pageSize: 10,
|
|
145
149
|
},
|
|
146
150
|
parameters: {
|
|
147
151
|
fetchMock: {
|
|
@@ -40,6 +40,7 @@ export interface PrevalenceOverTimeInnerProps {
|
|
|
40
40
|
views: View[];
|
|
41
41
|
confidenceIntervalMethods: ConfidenceIntervalMethod[];
|
|
42
42
|
lapisDateField: string;
|
|
43
|
+
pageSize: boolean | number;
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
export const PrevalenceOverTime: FunctionComponent<PrevalenceOverTimeProps> = ({
|
|
@@ -53,6 +54,7 @@ export const PrevalenceOverTime: FunctionComponent<PrevalenceOverTimeProps> = ({
|
|
|
53
54
|
height,
|
|
54
55
|
headline = 'Prevalence over time',
|
|
55
56
|
lapisDateField,
|
|
57
|
+
pageSize,
|
|
56
58
|
}) => {
|
|
57
59
|
const size = { height, width };
|
|
58
60
|
|
|
@@ -68,6 +70,7 @@ export const PrevalenceOverTime: FunctionComponent<PrevalenceOverTimeProps> = ({
|
|
|
68
70
|
views={views}
|
|
69
71
|
confidenceIntervalMethods={confidenceIntervalMethods}
|
|
70
72
|
lapisDateField={lapisDateField}
|
|
73
|
+
pageSize={pageSize}
|
|
71
74
|
/>
|
|
72
75
|
</Headline>
|
|
73
76
|
</ResizeContainer>
|
|
@@ -83,6 +86,7 @@ export const PrevalenceOverTimeInner: FunctionComponent<PrevalenceOverTimeInnerP
|
|
|
83
86
|
views,
|
|
84
87
|
confidenceIntervalMethods,
|
|
85
88
|
lapisDateField,
|
|
89
|
+
pageSize,
|
|
86
90
|
}) => {
|
|
87
91
|
const lapis = useContext(LapisUrlContext);
|
|
88
92
|
|
|
@@ -109,6 +113,7 @@ export const PrevalenceOverTimeInner: FunctionComponent<PrevalenceOverTimeInnerP
|
|
|
109
113
|
data={data}
|
|
110
114
|
granularity={granularity}
|
|
111
115
|
confidenceIntervalMethods={confidenceIntervalMethods}
|
|
116
|
+
pageSize={pageSize}
|
|
112
117
|
/>
|
|
113
118
|
);
|
|
114
119
|
};
|
|
@@ -118,6 +123,7 @@ type PrevalenceOverTimeTabsProps = {
|
|
|
118
123
|
data: PrevalenceOverTimeData;
|
|
119
124
|
granularity: TemporalGranularity;
|
|
120
125
|
confidenceIntervalMethods: ConfidenceIntervalMethod[];
|
|
126
|
+
pageSize: boolean | number;
|
|
121
127
|
};
|
|
122
128
|
|
|
123
129
|
const PrevalenceOverTimeTabs: FunctionComponent<PrevalenceOverTimeTabsProps> = ({
|
|
@@ -125,6 +131,7 @@ const PrevalenceOverTimeTabs: FunctionComponent<PrevalenceOverTimeTabsProps> = (
|
|
|
125
131
|
data,
|
|
126
132
|
granularity,
|
|
127
133
|
confidenceIntervalMethods,
|
|
134
|
+
pageSize,
|
|
128
135
|
}) => {
|
|
129
136
|
const [yAxisScaleType, setYAxisScaleType] = useState<ScaleType>('linear');
|
|
130
137
|
const [confidenceIntervalMethod, setConfidenceIntervalMethod] = useState<ConfidenceIntervalMethod>(
|
|
@@ -163,7 +170,7 @@ const PrevalenceOverTimeTabs: FunctionComponent<PrevalenceOverTimeTabsProps> = (
|
|
|
163
170
|
case 'table':
|
|
164
171
|
return {
|
|
165
172
|
title: 'Table',
|
|
166
|
-
content: <PrevalenceOverTimeTable data={data} granularity={granularity} />,
|
|
173
|
+
content: <PrevalenceOverTimeTable data={data} granularity={granularity} pageSize={pageSize} />,
|
|
167
174
|
};
|
|
168
175
|
}
|
|
169
176
|
};
|
|
@@ -4,7 +4,7 @@ import { queryAggregateData } from './queryAggregateData';
|
|
|
4
4
|
import { DUMMY_LAPIS_URL, lapisRequestMocks } from '../../vitest.setup';
|
|
5
5
|
|
|
6
6
|
describe('queryAggregateData', () => {
|
|
7
|
-
test('should fetch aggregate data', async () => {
|
|
7
|
+
test('should fetch aggregate data and sort initially by count descending when no initialSort is provided', async () => {
|
|
8
8
|
const fields = ['division', 'host'];
|
|
9
9
|
const filter = { country: 'USA' };
|
|
10
10
|
|
|
@@ -23,10 +23,124 @@ describe('queryAggregateData', () => {
|
|
|
23
23
|
const result = await queryAggregateData(filter, fields, DUMMY_LAPIS_URL);
|
|
24
24
|
|
|
25
25
|
expect(result).to.deep.equal([
|
|
26
|
+
{ proportion: 0.5, count: 16, region: 'region2', host: 'host2' },
|
|
27
|
+
{ proportion: 0.25, count: 8, region: 'region2', host: 'host1' },
|
|
26
28
|
{ proportion: 0.125, count: 4, region: 'region1', host: 'host1' },
|
|
27
29
|
{ proportion: 0.125, count: 4, region: 'region1', host: 'host2' },
|
|
28
|
-
{ proportion: 0.25, count: 8, region: 'region2', host: 'host1' },
|
|
29
|
-
{ proportion: 0.5, count: 16, region: 'region2', host: 'host2' },
|
|
30
30
|
]);
|
|
31
31
|
});
|
|
32
|
+
|
|
33
|
+
test('should sort by initialSort field ascending', async () => {
|
|
34
|
+
const fields = ['division', 'host'];
|
|
35
|
+
const filter = { country: 'USA' };
|
|
36
|
+
const initialSortField = 'host';
|
|
37
|
+
const initialSortDirection = 'ascending';
|
|
38
|
+
|
|
39
|
+
lapisRequestMocks.aggregated(
|
|
40
|
+
{ fields, ...filter },
|
|
41
|
+
{
|
|
42
|
+
data: [
|
|
43
|
+
{ count: 4, region: 'region1', host: 'A_host' },
|
|
44
|
+
{ count: 4, region: 'region1', host: 'B_host' },
|
|
45
|
+
{ count: 8, region: 'region2', host: 'A_host1' },
|
|
46
|
+
{ count: 16, region: 'region2', host: 'C_host' },
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const result = await queryAggregateData(filter, fields, DUMMY_LAPIS_URL, {
|
|
52
|
+
field: initialSortField,
|
|
53
|
+
direction: initialSortDirection,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
expect(result).to.deep.equal([
|
|
57
|
+
{ proportion: 0.125, count: 4, region: 'region1', host: 'A_host' },
|
|
58
|
+
{ proportion: 0.25, count: 8, region: 'region2', host: 'A_host1' },
|
|
59
|
+
{ proportion: 0.125, count: 4, region: 'region1', host: 'B_host' },
|
|
60
|
+
{ proportion: 0.5, count: 16, region: 'region2', host: 'C_host' },
|
|
61
|
+
]);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('should sort by initialSort field descending', async () => {
|
|
65
|
+
const fields = ['division', 'host'];
|
|
66
|
+
const filter = { country: 'USA' };
|
|
67
|
+
const initialSortField = 'host';
|
|
68
|
+
const initialSortDirection = 'descending';
|
|
69
|
+
|
|
70
|
+
lapisRequestMocks.aggregated(
|
|
71
|
+
{ fields, ...filter },
|
|
72
|
+
{
|
|
73
|
+
data: [
|
|
74
|
+
{ count: 4, region: 'region1', host: 'A_host' },
|
|
75
|
+
{ count: 4, region: 'region1', host: 'B_host' },
|
|
76
|
+
{ count: 8, region: 'region2', host: 'A_host1' },
|
|
77
|
+
{ count: 16, region: 'region2', host: 'C_host' },
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const result = await queryAggregateData(filter, fields, DUMMY_LAPIS_URL, {
|
|
83
|
+
field: initialSortField,
|
|
84
|
+
direction: initialSortDirection,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
expect(result).to.deep.equal([
|
|
88
|
+
{ proportion: 0.5, count: 16, region: 'region2', host: 'C_host' },
|
|
89
|
+
{ proportion: 0.125, count: 4, region: 'region1', host: 'B_host' },
|
|
90
|
+
{ proportion: 0.25, count: 8, region: 'region2', host: 'A_host1' },
|
|
91
|
+
{ proportion: 0.125, count: 4, region: 'region1', host: 'A_host' },
|
|
92
|
+
]);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('should sort by initialSort number field', async () => {
|
|
96
|
+
const fields = ['division', 'host'];
|
|
97
|
+
const filter = { country: 'USA' };
|
|
98
|
+
const initialSortField = 'proportion';
|
|
99
|
+
const initialSortDirection = 'descending';
|
|
100
|
+
|
|
101
|
+
lapisRequestMocks.aggregated(
|
|
102
|
+
{ fields, ...filter },
|
|
103
|
+
{
|
|
104
|
+
data: [
|
|
105
|
+
{ count: 4, region: 'region1', host: 'A_host' },
|
|
106
|
+
{ count: 4, region: 'region1', host: 'B_host' },
|
|
107
|
+
{ count: 8, region: 'region2', host: 'A_host1' },
|
|
108
|
+
{ count: 16, region: 'region2', host: 'C_host' },
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const result = await queryAggregateData(filter, fields, DUMMY_LAPIS_URL, {
|
|
114
|
+
field: initialSortField,
|
|
115
|
+
direction: initialSortDirection,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
expect(result).to.deep.equal([
|
|
119
|
+
{ proportion: 0.125, count: 4, region: 'region1', host: 'A_host' },
|
|
120
|
+
{ proportion: 0.125, count: 4, region: 'region1', host: 'B_host' },
|
|
121
|
+
{ proportion: 0.25, count: 8, region: 'region2', host: 'A_host1' },
|
|
122
|
+
{ proportion: 0.5, count: 16, region: 'region2', host: 'C_host' },
|
|
123
|
+
]);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('should throw if initialSortField is not in fields', async () => {
|
|
127
|
+
const fields = ['division', 'host'];
|
|
128
|
+
const filter = { country: 'USA' };
|
|
129
|
+
const initialSortField = 'not_in_fields';
|
|
130
|
+
const initialSortDirection = 'descending';
|
|
131
|
+
|
|
132
|
+
lapisRequestMocks.aggregated(
|
|
133
|
+
{ fields, ...filter },
|
|
134
|
+
{
|
|
135
|
+
data: [{ count: 4, region: 'region1', host: 'A_host' }],
|
|
136
|
+
},
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
await expect(
|
|
140
|
+
queryAggregateData(filter, fields, DUMMY_LAPIS_URL, {
|
|
141
|
+
field: initialSortField,
|
|
142
|
+
direction: initialSortDirection,
|
|
143
|
+
}),
|
|
144
|
+
).rejects.toThrowError('InitialSort field not in fields. Valid fields are: count, proportion, division, host');
|
|
145
|
+
});
|
|
32
146
|
});
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { FetchAggregatedOperator } from '../operator/FetchAggregatedOperator';
|
|
2
|
+
import { SortOperator } from '../operator/SortOperator';
|
|
3
|
+
import { type InitialSort } from '../preact/aggregatedData/aggregate';
|
|
2
4
|
import { type LapisFilter } from '../types';
|
|
3
5
|
|
|
4
6
|
export type AggregateData = (Record<string, string | null | number | boolean> & {
|
|
@@ -6,9 +8,36 @@ export type AggregateData = (Record<string, string | null | number | boolean> &
|
|
|
6
8
|
proportion: number;
|
|
7
9
|
})[];
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
const compareAscending = (a: string | null | number, b: string | null | number) => {
|
|
12
|
+
if (typeof a === 'number' && typeof b === 'number') {
|
|
13
|
+
return a - b;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const strA = a != null ? String(a) : '';
|
|
17
|
+
const strB = b != null ? String(b) : '';
|
|
18
|
+
|
|
19
|
+
return strA.localeCompare(strB);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export async function queryAggregateData(
|
|
23
|
+
variant: LapisFilter,
|
|
24
|
+
fields: string[],
|
|
25
|
+
lapis: string,
|
|
26
|
+
initialSort: InitialSort = { field: 'count', direction: 'descending' },
|
|
27
|
+
signal?: AbortSignal,
|
|
28
|
+
) {
|
|
29
|
+
const validSortFields = ['count', 'proportion', ...fields];
|
|
30
|
+
if (!validSortFields.includes(initialSort.field)) {
|
|
31
|
+
throw new Error(`InitialSort field not in fields. Valid fields are: ${validSortFields.join(', ')}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
10
34
|
const fetchData = new FetchAggregatedOperator<Record<string, string | null | number>>(variant, fields);
|
|
11
|
-
const
|
|
35
|
+
const sortData = new SortOperator(fetchData, (a, b) => {
|
|
36
|
+
return initialSort.direction === 'ascending'
|
|
37
|
+
? compareAscending(a[initialSort.field], b[initialSort.field])
|
|
38
|
+
: compareAscending(b[initialSort.field], a[initialSort.field]);
|
|
39
|
+
});
|
|
40
|
+
const data = (await sortData.evaluate(lapis, signal)).content;
|
|
12
41
|
|
|
13
42
|
const total = data.reduce((acc, row) => acc + row.count, 0);
|
|
14
43
|
|
|
@@ -17,6 +17,7 @@ const codeExample = String.raw`
|
|
|
17
17
|
fields='["region", "country"]'
|
|
18
18
|
initialValue='Europe / Switzerland'
|
|
19
19
|
width="100%"
|
|
20
|
+
placeholderText="Enter a location"
|
|
20
21
|
></gs-location-filter>`;
|
|
21
22
|
|
|
22
23
|
const meta: Meta = {
|
|
@@ -48,6 +49,11 @@ const meta: Meta = {
|
|
|
48
49
|
type: 'text',
|
|
49
50
|
},
|
|
50
51
|
},
|
|
52
|
+
placeholderText: {
|
|
53
|
+
control: {
|
|
54
|
+
type: 'text',
|
|
55
|
+
},
|
|
56
|
+
},
|
|
51
57
|
},
|
|
52
58
|
decorators: [withActions],
|
|
53
59
|
tags: ['autodocs'],
|
|
@@ -63,6 +69,7 @@ const Template: StoryObj<LocationFilterProps> = {
|
|
|
63
69
|
.fields=${args.fields}
|
|
64
70
|
initialValue=${ifDefined(args.initialValue)}
|
|
65
71
|
.width=${args.width}
|
|
72
|
+
placeholderText=${ifDefined(args.placeholderText)}
|
|
66
73
|
></gs-location-filter>
|
|
67
74
|
</div>
|
|
68
75
|
</gs-app>`;
|
|
@@ -71,6 +78,7 @@ const Template: StoryObj<LocationFilterProps> = {
|
|
|
71
78
|
fields: ['region', 'country', 'division', 'location'],
|
|
72
79
|
initialValue: '',
|
|
73
80
|
width: '100%',
|
|
81
|
+
placeholderText: 'Enter a location',
|
|
74
82
|
},
|
|
75
83
|
};
|
|
76
84
|
|
|
@@ -102,6 +110,9 @@ export const LocationFilter: StoryObj<LocationFilterProps> = {
|
|
|
102
110
|
await waitFor(() => {
|
|
103
111
|
return expect(canvas.getByRole('combobox')).toBeEnabled();
|
|
104
112
|
});
|
|
113
|
+
await waitFor(() => {
|
|
114
|
+
return expect(canvas.getByPlaceholderText('Enter a location')).toBeInTheDocument();
|
|
115
|
+
});
|
|
105
116
|
},
|
|
106
117
|
};
|
|
107
118
|
|
|
@@ -59,8 +59,21 @@ export class LocationFilterComponent extends PreactLitAdapter {
|
|
|
59
59
|
@property({ type: String })
|
|
60
60
|
width: string = '100%';
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* The placeholder text to display in the input field, if it is empty.
|
|
64
|
+
*/
|
|
65
|
+
@property()
|
|
66
|
+
placeholderText: string = '';
|
|
67
|
+
|
|
62
68
|
override render() {
|
|
63
|
-
return
|
|
69
|
+
return (
|
|
70
|
+
<LocationFilter
|
|
71
|
+
initialValue={this.initialValue}
|
|
72
|
+
fields={this.fields}
|
|
73
|
+
width={this.width}
|
|
74
|
+
placeholderText={this.placeholderText}
|
|
75
|
+
/>
|
|
76
|
+
);
|
|
64
77
|
}
|
|
65
78
|
}
|
|
66
79
|
|
|
@@ -17,6 +17,9 @@ const codeExample = `
|
|
|
17
17
|
headline="Aggregate"
|
|
18
18
|
width='100%'
|
|
19
19
|
height='700px'
|
|
20
|
+
initialSortField="count"
|
|
21
|
+
initialSortDirection="descending"
|
|
22
|
+
pageSize="10"
|
|
20
23
|
></gs-aggregate>`;
|
|
21
24
|
|
|
22
25
|
const meta: Meta<Required<AggregateProps>> = {
|
|
@@ -31,6 +34,12 @@ const meta: Meta<Required<AggregateProps>> = {
|
|
|
31
34
|
width: { control: 'text' },
|
|
32
35
|
height: { control: 'text' },
|
|
33
36
|
headline: { control: 'text' },
|
|
37
|
+
pageSize: { control: 'object' },
|
|
38
|
+
initialSortField: { control: 'text' },
|
|
39
|
+
initialSortDirection: {
|
|
40
|
+
options: ['ascending', 'descending'],
|
|
41
|
+
control: { type: 'radio' },
|
|
42
|
+
},
|
|
34
43
|
},
|
|
35
44
|
parameters: withComponentDocs({
|
|
36
45
|
fetchMock: {
|
|
@@ -72,6 +81,9 @@ export const Table: StoryObj<Required<AggregateProps>> = {
|
|
|
72
81
|
.width=${args.width}
|
|
73
82
|
.height=${args.height}
|
|
74
83
|
.headline=${args.headline}
|
|
84
|
+
.initialSortField=${args.initialSortField}
|
|
85
|
+
.initialSortDirection=${args.initialSortDirection}
|
|
86
|
+
.pageSize=${args.pageSize}
|
|
75
87
|
></gs-aggregate>
|
|
76
88
|
</gs-app>
|
|
77
89
|
`,
|
|
@@ -84,5 +96,8 @@ export const Table: StoryObj<Required<AggregateProps>> = {
|
|
|
84
96
|
width: '100%',
|
|
85
97
|
height: '700px',
|
|
86
98
|
headline: 'Aggregate',
|
|
99
|
+
initialSortField: 'count',
|
|
100
|
+
initialSortDirection: 'descending',
|
|
101
|
+
pageSize: 10,
|
|
87
102
|
},
|
|
88
103
|
};
|
|
@@ -67,6 +67,26 @@ export class AggregateComponent extends PreactLitAdapterWithGridJsStyles {
|
|
|
67
67
|
@property({ type: String })
|
|
68
68
|
headline: string = 'Aggregate';
|
|
69
69
|
|
|
70
|
+
/**
|
|
71
|
+
* The field by which the table is initially sorted.
|
|
72
|
+
* Must be one of the fields specified in the fields property, 'count', or 'proportion'.
|
|
73
|
+
*/
|
|
74
|
+
@property({ type: String })
|
|
75
|
+
initialSortField: string = 'count';
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The initial sort direction of the table.
|
|
79
|
+
*/
|
|
80
|
+
@property({ type: String })
|
|
81
|
+
initialSortDirection: 'ascending' | 'descending' = 'descending';
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The maximum number of rows to display in the table view.
|
|
85
|
+
* Set to `false` to disable pagination. Set to `true` to enable pagination with a default limit (10).
|
|
86
|
+
*/
|
|
87
|
+
@property({ type: Object })
|
|
88
|
+
pageSize: boolean | number = false;
|
|
89
|
+
|
|
70
90
|
override render() {
|
|
71
91
|
return (
|
|
72
92
|
<Aggregate
|
|
@@ -76,6 +96,9 @@ export class AggregateComponent extends PreactLitAdapterWithGridJsStyles {
|
|
|
76
96
|
width={this.width}
|
|
77
97
|
height={this.height}
|
|
78
98
|
headline={this.headline}
|
|
99
|
+
initialSortField={this.initialSortField}
|
|
100
|
+
initialSortDirection={this.initialSortDirection}
|
|
101
|
+
pageSize={this.pageSize}
|
|
79
102
|
/>
|
|
80
103
|
);
|
|
81
104
|
}
|
|
@@ -19,6 +19,7 @@ const codeExample = String.raw`
|
|
|
19
19
|
headline="Mutation comparison"
|
|
20
20
|
width='100%'
|
|
21
21
|
height='700px'
|
|
22
|
+
pageSize="10"
|
|
22
23
|
></gs-mutation-comparison>`;
|
|
23
24
|
|
|
24
25
|
const meta: Meta<Required<MutationComparisonProps>> = {
|
|
@@ -37,6 +38,7 @@ const meta: Meta<Required<MutationComparisonProps>> = {
|
|
|
37
38
|
width: { control: 'text' },
|
|
38
39
|
height: { control: 'text' },
|
|
39
40
|
headline: { control: 'text' },
|
|
41
|
+
pageSize: { control: 'object' },
|
|
40
42
|
},
|
|
41
43
|
parameters: withComponentDocs({
|
|
42
44
|
componentDocs: {
|
|
@@ -60,6 +62,7 @@ const Template: StoryObj<Required<MutationComparisonProps>> = {
|
|
|
60
62
|
.width=${args.width}
|
|
61
63
|
.height=${args.height}
|
|
62
64
|
.headline=${args.headline}
|
|
65
|
+
.pageSize=${args.pageSize}
|
|
63
66
|
></gs-mutation-comparison>
|
|
64
67
|
</gs-app>
|
|
65
68
|
`,
|
|
@@ -91,6 +94,7 @@ export const Default: StoryObj<Required<MutationComparisonProps>> = {
|
|
|
91
94
|
width: '100%',
|
|
92
95
|
height: '700px',
|
|
93
96
|
headline: 'Mutation comparison',
|
|
97
|
+
pageSize: 10,
|
|
94
98
|
},
|
|
95
99
|
parameters: {
|
|
96
100
|
fetchMock: {
|
|
@@ -82,6 +82,13 @@ export class MutationComparisonComponent extends PreactLitAdapterWithGridJsStyle
|
|
|
82
82
|
@property({ type: String })
|
|
83
83
|
headline: string = 'Mutation comparison';
|
|
84
84
|
|
|
85
|
+
/**
|
|
86
|
+
* The maximum number of rows to display in the table view.
|
|
87
|
+
* Set to `false` to disable pagination. Set to `true` to enable pagination with a default limit (10).
|
|
88
|
+
*/
|
|
89
|
+
@property({ type: Object })
|
|
90
|
+
pageSize: boolean | number = false;
|
|
91
|
+
|
|
85
92
|
override render() {
|
|
86
93
|
return (
|
|
87
94
|
<MutationComparison
|
|
@@ -91,6 +98,7 @@ export class MutationComparisonComponent extends PreactLitAdapterWithGridJsStyle
|
|
|
91
98
|
width={this.width}
|
|
92
99
|
height={this.height}
|
|
93
100
|
headline={this.headline}
|
|
101
|
+
pageSize={this.pageSize}
|
|
94
102
|
/>
|
|
95
103
|
);
|
|
96
104
|
}
|
|
@@ -19,6 +19,7 @@ const codeExample = String.raw`
|
|
|
19
19
|
headline="Mutations"
|
|
20
20
|
width='100%'
|
|
21
21
|
height='700px'
|
|
22
|
+
pageSize="10"
|
|
22
23
|
></gs-mutations>`;
|
|
23
24
|
|
|
24
25
|
const meta: Meta<Required<MutationsProps>> = {
|
|
@@ -37,6 +38,7 @@ const meta: Meta<Required<MutationsProps>> = {
|
|
|
37
38
|
width: { control: 'text' },
|
|
38
39
|
height: { control: 'text' },
|
|
39
40
|
headline: { control: 'text' },
|
|
41
|
+
pageSize: { control: 'object' },
|
|
40
42
|
},
|
|
41
43
|
args: {
|
|
42
44
|
variant: { country: 'Switzerland', pangoLineage: 'B.1.1.7', dateTo: '2022-01-01' },
|
|
@@ -45,6 +47,7 @@ const meta: Meta<Required<MutationsProps>> = {
|
|
|
45
47
|
width: '100%',
|
|
46
48
|
height: '700px',
|
|
47
49
|
headline: 'Mutations',
|
|
50
|
+
pageSize: 10,
|
|
48
51
|
},
|
|
49
52
|
parameters: withComponentDocs({
|
|
50
53
|
componentDocs: {
|
|
@@ -68,6 +71,7 @@ const Template: StoryObj<Required<MutationsProps>> = {
|
|
|
68
71
|
.width=${args.width}
|
|
69
72
|
.height=${args.height}
|
|
70
73
|
.headline=${args.headline}
|
|
74
|
+
.pageSize=${args.pageSize}
|
|
71
75
|
></gs-mutations>
|
|
72
76
|
</gs-app>
|
|
73
77
|
`,
|
|
@@ -77,6 +77,13 @@ export class MutationsComponent extends PreactLitAdapterWithGridJsStyles {
|
|
|
77
77
|
@property({ type: String })
|
|
78
78
|
headline: string = 'Mutations';
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* The maximum number of rows to display in the table view.
|
|
82
|
+
* Set to `false` to disable pagination. Set to `true` to enable pagination with a default limit (10).
|
|
83
|
+
*/
|
|
84
|
+
@property({ type: Object })
|
|
85
|
+
pageSize: boolean | number = false;
|
|
86
|
+
|
|
80
87
|
override render() {
|
|
81
88
|
return (
|
|
82
89
|
<Mutations
|
|
@@ -86,6 +93,7 @@ export class MutationsComponent extends PreactLitAdapterWithGridJsStyles {
|
|
|
86
93
|
width={this.width}
|
|
87
94
|
height={this.height}
|
|
88
95
|
headline={this.headline}
|
|
96
|
+
pageSize={this.pageSize}
|
|
89
97
|
/>
|
|
90
98
|
);
|
|
91
99
|
}
|
|
@@ -26,6 +26,7 @@ const codeExample = String.raw`
|
|
|
26
26
|
width="100%"
|
|
27
27
|
height="700px"
|
|
28
28
|
lapisDateField="date"
|
|
29
|
+
pageSize="10"
|
|
29
30
|
></gs-prevalence-over-time>`;
|
|
30
31
|
|
|
31
32
|
const meta: Meta<Required<PrevalenceOverTimeProps>> = {
|
|
@@ -50,6 +51,7 @@ const meta: Meta<Required<PrevalenceOverTimeProps>> = {
|
|
|
50
51
|
width: { control: 'text' },
|
|
51
52
|
height: { control: 'text' },
|
|
52
53
|
headline: { control: 'text' },
|
|
54
|
+
pageSize: { control: 'object' },
|
|
53
55
|
},
|
|
54
56
|
parameters: withComponentDocs({
|
|
55
57
|
componentDocs: {
|
|
@@ -77,6 +79,7 @@ const Template: StoryObj<Required<PrevalenceOverTimeProps>> = {
|
|
|
77
79
|
.height=${args.height}
|
|
78
80
|
.headline=${args.headline}
|
|
79
81
|
.lapisDateField=${args.lapisDateField}
|
|
82
|
+
.pageSize=${args.pageSize}
|
|
80
83
|
></gs-prevalence-over-time>
|
|
81
84
|
</gs-app>
|
|
82
85
|
`,
|
|
@@ -98,6 +101,7 @@ export const TwoVariants: StoryObj<Required<PrevalenceOverTimeProps>> = {
|
|
|
98
101
|
height: '700px',
|
|
99
102
|
headline: 'Prevalence over time',
|
|
100
103
|
lapisDateField: 'date',
|
|
104
|
+
pageSize: 10,
|
|
101
105
|
},
|
|
102
106
|
parameters: {
|
|
103
107
|
fetchMock: {
|
|
@@ -170,6 +174,7 @@ export const OneVariant: StoryObj<Required<PrevalenceOverTimeProps>> = {
|
|
|
170
174
|
height: '700px',
|
|
171
175
|
headline: 'Prevalence over time',
|
|
172
176
|
lapisDateField: 'date',
|
|
177
|
+
pageSize: 10,
|
|
173
178
|
},
|
|
174
179
|
parameters: {
|
|
175
180
|
fetchMock: {
|