@genspectrum/dashboard-components 0.14.2 → 0.16.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 +281 -105
- package/dist/{LineageFilterChangedEvent-C9dXOxt6.js → LineageFilterChangedEvent-COWV-Y0k.js} +6 -6
- package/dist/LineageFilterChangedEvent-COWV-Y0k.js.map +1 -0
- package/dist/assets/{mutationOverTimeWorker-Dxnxrfe0.js.map → mutationOverTimeWorker-BL50C-yi.js.map} +1 -1
- package/dist/components.d.ts +40 -40
- package/dist/components.js +373 -243
- package/dist/components.js.map +1 -1
- package/dist/style.css +9 -0
- package/dist/util.d.ts +49 -49
- package/dist/util.js +2 -2
- package/package.json +1 -1
- package/src/preact/ReferenceGenomeContext.ts +14 -1
- package/src/preact/aggregatedData/aggregate-bar-chart.tsx +26 -5
- package/src/preact/aggregatedData/aggregate.stories.tsx +0 -1
- package/src/preact/aggregatedData/aggregate.tsx +5 -1
- package/src/preact/components/ReferenceGenomesAwaiter.tsx +1 -6
- package/src/preact/components/resize-container.tsx +1 -1
- package/src/preact/{dateRangeSelector/date-range-selector.stories.tsx → dateRangeFilter/date-range-filter.stories.tsx} +15 -15
- package/src/preact/{dateRangeSelector/date-range-selector.tsx → dateRangeFilter/date-range-filter.tsx} +9 -9
- package/src/preact/{dateRangeSelector → dateRangeFilter}/dateRangeOption.ts +2 -2
- package/src/preact/mutationComparison/mutation-comparison-venn.tsx +4 -2
- package/src/preact/mutationComparison/mutation-comparison.stories.tsx +0 -1
- package/src/preact/mutationComparison/mutation-comparison.tsx +5 -1
- package/src/preact/mutationFilter/mutation-filter.stories.tsx +17 -1
- package/src/preact/mutationFilter/mutation-filter.tsx +8 -0
- package/src/preact/mutations/mutations.stories.tsx +0 -1
- package/src/preact/mutations/mutations.tsx +1 -1
- package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +0 -2
- package/src/preact/mutationsOverTime/mutations-over-time.tsx +1 -1
- package/src/preact/numberSequencesOverTime/number-sequences-over-time-bar-chart.tsx +8 -3
- package/src/preact/numberSequencesOverTime/number-sequences-over-time-line-chart.tsx +8 -3
- package/src/preact/numberSequencesOverTime/number-sequences-over-time.stories.tsx +3 -1
- package/src/preact/numberSequencesOverTime/number-sequences-over-time.tsx +18 -3
- package/src/preact/prevalenceOverTime/prevalence-over-time-bar-chart.tsx +48 -35
- package/src/preact/prevalenceOverTime/prevalence-over-time-bubble-chart.tsx +83 -70
- package/src/preact/prevalenceOverTime/prevalence-over-time-line-chart.tsx +48 -37
- package/src/preact/prevalenceOverTime/prevalence-over-time.stories.tsx +0 -3
- package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +6 -1
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage-chart.tsx +31 -23
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.stories.tsx +0 -1
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +5 -1
- package/src/preact/sequencesByLocation/__mockData__/worldAtlas.json +1 -0
- package/src/preact/{map → sequencesByLocation}/sequences-by-location-map.tsx +6 -3
- package/src/preact/{map → sequencesByLocation}/sequences-by-location-table.tsx +1 -1
- package/src/preact/{map → sequencesByLocation}/sequences-by-location.stories.tsx +58 -1
- package/src/preact/{map → sequencesByLocation}/sequences-by-location.tsx +10 -1
- package/src/preact/shared/aspectRatio/AspectRatio.tsx +13 -0
- package/src/preact/shared/charts/getMaintainAspectRatio.ts +3 -0
- package/src/preact/statistic/statistics.stories.tsx +0 -1
- package/src/preact/statistic/statistics.tsx +4 -4
- package/src/preact/{textInput/TextInputChangedEvent.ts → textFilter/TextFilterChangedEvent.ts} +2 -2
- package/src/preact/{textInput/text-input.stories.tsx → textFilter/text-filter.stories.tsx} +10 -10
- package/src/preact/{textInput/text-input.tsx → textFilter/text-filter.tsx} +10 -10
- package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.stories.tsx +0 -1
- package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.tsx +1 -1
- package/src/query/computeMapLocationData.spec.ts +1 -1
- package/src/query/computeMapLocationData.ts +1 -1
- package/src/query/querySequencesByLocationData.ts +1 -1
- package/src/utilEntrypoint.ts +3 -3
- package/src/web-components/ResizeContainer.mdx +4 -1
- package/src/web-components/input/{gs-date-range-selector.stories.ts → gs-date-range-filter.stories.ts} +59 -14
- package/src/web-components/input/{gs-date-range-selector.tsx → gs-date-range-filter.tsx} +28 -13
- package/src/web-components/input/{gs-text-input.stories.ts → gs-text-filter.stories.ts} +15 -15
- package/src/web-components/input/{gs-text-input.tsx → gs-text-filter.tsx} +16 -16
- package/src/web-components/input/index.ts +2 -2
- package/src/web-components/visualization/gs-aggregate.stories.ts +13 -6
- package/src/web-components/visualization/gs-aggregate.tsx +1 -1
- package/src/web-components/visualization/gs-mutation-comparison.stories.ts +8 -1
- package/src/web-components/visualization/gs-mutation-comparison.tsx +1 -1
- package/src/web-components/visualization/gs-mutations-over-time.stories.ts +9 -1
- package/src/web-components/visualization/gs-mutations-over-time.tsx +1 -1
- package/src/web-components/visualization/gs-mutations.stories.ts +8 -1
- package/src/web-components/visualization/gs-mutations.tsx +1 -1
- package/src/web-components/visualization/gs-number-sequences-over-time.stories.ts +11 -1
- package/src/web-components/visualization/gs-number-sequences-over-time.tsx +1 -1
- package/src/web-components/visualization/gs-prevalence-over-time.stories.ts +8 -2
- package/src/web-components/visualization/gs-prevalence-over-time.tsx +1 -1
- package/src/web-components/visualization/gs-relative-growth-advantage.stories.ts +8 -1
- package/src/web-components/visualization/gs-relative-growth-advantage.tsx +1 -1
- package/src/web-components/visualization/gs-sequences-by-location.stories.ts +13 -7
- package/src/web-components/visualization/gs-sequences-by-location.tsx +6 -3
- package/src/web-components/visualization/gs-statistics.stories.ts +0 -1
- package/src/web-components/visualization/gs-statistics.tsx +1 -1
- package/src/web-components/wastewaterVisualization/gs-wastewater-mutations-over-time.stories.ts +9 -1
- package/src/web-components/wastewaterVisualization/gs-wastewater-mutations-over-time.tsx +1 -1
- package/standalone-bundle/assets/{mutationOverTimeWorker-CmSrq4SZ.js.map → mutationOverTimeWorker-CFB5-Mdk.js.map} +1 -1
- package/standalone-bundle/dashboard-components.js +6032 -5937
- package/standalone-bundle/dashboard-components.js.map +1 -1
- package/standalone-bundle/style.css +1 -1
- package/dist/LineageFilterChangedEvent-C9dXOxt6.js.map +0 -1
- package/src/preact/map/__mockData__/worldAtlas.json +0 -497127
- /package/src/preact/{dateRangeSelector → dateRangeFilter}/computeInitialValues.spec.ts +0 -0
- /package/src/preact/{dateRangeSelector → dateRangeFilter}/computeInitialValues.ts +0 -0
- /package/src/preact/{dateRangeSelector → dateRangeFilter}/dateConversion.ts +0 -0
- /package/src/preact/{dateRangeSelector → dateRangeFilter}/selectableOptions.ts +0 -0
- /package/src/preact/{map → sequencesByLocation}/__mockData__/aggregatedGermany.json +0 -0
- /package/src/preact/{map → sequencesByLocation}/__mockData__/aggregatedWorld.json +0 -0
- /package/src/preact/{map → sequencesByLocation}/__mockData__/germanyMap.json +0 -0
- /package/src/preact/{map → sequencesByLocation}/__mockData__/howToGenerateWorldMap.md +0 -0
- /package/src/preact/{map → sequencesByLocation}/leafletStyleModifications.css +0 -0
- /package/src/preact/{map → sequencesByLocation}/loadMapSource.tsx +0 -0
- /package/src/preact/{textInput → textFilter}/__mockData__/aggregated_hosts.json +0 -0
- /package/src/preact/{textInput → textFilter}/fetchStringAutocompleteList.spec.ts +0 -0
- /package/src/preact/{textInput → textFilter}/fetchStringAutocompleteList.ts +0 -0
|
@@ -6,6 +6,7 @@ import { useEffect, useRef } from 'preact/hooks';
|
|
|
6
6
|
import type { EnhancedGeoJsonFeatureProperties } from '../../query/computeMapLocationData';
|
|
7
7
|
import { InfoHeadline1, InfoParagraph } from '../components/info';
|
|
8
8
|
import { Modal, useModalRef } from '../components/modal';
|
|
9
|
+
import { AspectRatio } from '../shared/aspectRatio/AspectRatio';
|
|
9
10
|
import { formatProportion } from '../shared/table/formatProportion';
|
|
10
11
|
|
|
11
12
|
type SequencesByLocationMapProps = {
|
|
@@ -20,6 +21,7 @@ type SequencesByLocationMapProps = {
|
|
|
20
21
|
offsetX: number;
|
|
21
22
|
offsetY: number;
|
|
22
23
|
hasTableView: boolean;
|
|
24
|
+
maintainAspectRatio: boolean;
|
|
23
25
|
};
|
|
24
26
|
|
|
25
27
|
export const SequencesByLocationMap: FunctionComponent<SequencesByLocationMapProps> = ({
|
|
@@ -34,6 +36,7 @@ export const SequencesByLocationMap: FunctionComponent<SequencesByLocationMapPro
|
|
|
34
36
|
offsetX,
|
|
35
37
|
offsetY,
|
|
36
38
|
hasTableView,
|
|
39
|
+
maintainAspectRatio,
|
|
37
40
|
}) => {
|
|
38
41
|
const ref = useRef<HTMLDivElement>(null);
|
|
39
42
|
|
|
@@ -69,8 +72,8 @@ export const SequencesByLocationMap: FunctionComponent<SequencesByLocationMapPro
|
|
|
69
72
|
}, [ref, locations, enableMapNavigation, lapisLocationField, zoom, offsetX, offsetY]);
|
|
70
73
|
|
|
71
74
|
return (
|
|
72
|
-
<
|
|
73
|
-
<div
|
|
75
|
+
<AspectRatio aspectRatio={maintainAspectRatio ? 50 : undefined}>
|
|
76
|
+
<div className='h-full' ref={ref} />
|
|
74
77
|
<div className='relative'>
|
|
75
78
|
<DataMatchInformation
|
|
76
79
|
totalCount={totalCount}
|
|
@@ -80,7 +83,7 @@ export const SequencesByLocationMap: FunctionComponent<SequencesByLocationMapPro
|
|
|
80
83
|
hasTableView={hasTableView}
|
|
81
84
|
/>
|
|
82
85
|
</div>
|
|
83
|
-
</
|
|
86
|
+
</AspectRatio>
|
|
84
87
|
);
|
|
85
88
|
};
|
|
86
89
|
|
|
@@ -32,7 +32,7 @@ export const SequencesByLocationTable: FunctionComponent<SequencesByLocationTabl
|
|
|
32
32
|
sort: true,
|
|
33
33
|
formatter: (cell: number) => formatProportion(cell),
|
|
34
34
|
},
|
|
35
|
-
...('isShownOnMap' in tableData[0]
|
|
35
|
+
...(tableData.length > 0 && 'isShownOnMap' in tableData[0]
|
|
36
36
|
? [{ id: 'isShownOnMap', name: 'shown on map', sort: true, width: '20%' }]
|
|
37
37
|
: []),
|
|
38
38
|
];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Meta, type StoryObj } from '@storybook/preact';
|
|
2
|
+
import { expect, waitFor, within } from '@storybook/test';
|
|
2
3
|
|
|
3
4
|
import worldAtlas from './__mockData__/worldAtlas.json';
|
|
4
5
|
import { AGGREGATED_ENDPOINT, LAPIS_URL } from '../../constants';
|
|
@@ -13,6 +14,21 @@ import './leafletStyleModifications.css';
|
|
|
13
14
|
const meta: Meta<SequencesByLocationProps> = {
|
|
14
15
|
title: 'Visualization/SequencesByLocation',
|
|
15
16
|
component: SequencesByLocation,
|
|
17
|
+
argTypes: {
|
|
18
|
+
lapisFilter: { control: 'object' },
|
|
19
|
+
lapisLocationField: { control: { type: 'text' } },
|
|
20
|
+
enableMapNavigation: { control: { type: 'boolean' } },
|
|
21
|
+
width: { control: { type: 'text' } },
|
|
22
|
+
height: { control: { type: 'text' } },
|
|
23
|
+
views: {
|
|
24
|
+
options: ['map', 'table'],
|
|
25
|
+
control: { type: 'check' },
|
|
26
|
+
},
|
|
27
|
+
zoom: { control: { type: 'number' } },
|
|
28
|
+
offsetX: { control: { type: 'number' } },
|
|
29
|
+
offsetY: { control: { type: 'number' } },
|
|
30
|
+
pageSize: { control: 'object' },
|
|
31
|
+
},
|
|
16
32
|
};
|
|
17
33
|
|
|
18
34
|
export default meta;
|
|
@@ -51,7 +67,6 @@ export const Default: StoryObj<SequencesByLocationProps> = {
|
|
|
51
67
|
},
|
|
52
68
|
enableMapNavigation: false,
|
|
53
69
|
width: '1100px',
|
|
54
|
-
height: '800px',
|
|
55
70
|
views: ['map', 'table'],
|
|
56
71
|
zoom: 2,
|
|
57
72
|
offsetX: 0,
|
|
@@ -77,6 +92,48 @@ export const Default: StoryObj<SequencesByLocationProps> = {
|
|
|
77
92
|
},
|
|
78
93
|
};
|
|
79
94
|
|
|
95
|
+
export const NoData: StoryObj<SequencesByLocationProps> = {
|
|
96
|
+
...Default,
|
|
97
|
+
parameters: {
|
|
98
|
+
fetchMock: {
|
|
99
|
+
mocks: [
|
|
100
|
+
{
|
|
101
|
+
matcher: {
|
|
102
|
+
name: 'worldMap',
|
|
103
|
+
url: worldMapUrl,
|
|
104
|
+
},
|
|
105
|
+
response: {
|
|
106
|
+
status: 200,
|
|
107
|
+
body: worldAtlas,
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
matcher: {
|
|
112
|
+
name: 'aggregatedData',
|
|
113
|
+
url: AGGREGATED_ENDPOINT,
|
|
114
|
+
body: {
|
|
115
|
+
fields: ['country'],
|
|
116
|
+
dateFrom: '2022-01-01',
|
|
117
|
+
dateTo: '2022-04-01',
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
response: {
|
|
121
|
+
status: 200,
|
|
122
|
+
body: { data: [] },
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
play: async ({ canvasElement }) => {
|
|
129
|
+
const canvas = within(canvasElement);
|
|
130
|
+
|
|
131
|
+
await waitFor(async () => {
|
|
132
|
+
await expect(canvas.getByText('No data available.')).toBeVisible();
|
|
133
|
+
});
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
|
|
80
137
|
export const InvalidTopoJsonTopology: StoryObj<SequencesByLocationProps> = {
|
|
81
138
|
...Default,
|
|
82
139
|
parameters: {
|
|
@@ -20,7 +20,9 @@ import { ResizeContainer } from '../components/resize-container';
|
|
|
20
20
|
import { useQuery } from '../useQuery';
|
|
21
21
|
import { mapSourceSchema } from './loadMapSource';
|
|
22
22
|
import { lapisFilterSchema, views } from '../../types';
|
|
23
|
+
import { NoDataDisplay } from '../components/no-data-display';
|
|
23
24
|
import Tabs from '../components/tabs';
|
|
25
|
+
import { getMaintainAspectRatio } from '../shared/charts/getMaintainAspectRatio';
|
|
24
26
|
|
|
25
27
|
export const sequencesByLocationViewSchema = z.union([z.literal(views.map), z.literal(views.table)]);
|
|
26
28
|
export type SequencesByLocationMapView = z.infer<typeof sequencesByLocationViewSchema>;
|
|
@@ -31,7 +33,7 @@ const sequencesByLocationPropsSchema = z.object({
|
|
|
31
33
|
mapSource: mapSourceSchema.optional(),
|
|
32
34
|
enableMapNavigation: z.boolean(),
|
|
33
35
|
width: z.string(),
|
|
34
|
-
height: z.string(),
|
|
36
|
+
height: z.string().optional(),
|
|
35
37
|
views: z.array(sequencesByLocationViewSchema),
|
|
36
38
|
zoom: z.number(),
|
|
37
39
|
offsetX: z.number(),
|
|
@@ -75,6 +77,10 @@ const SequencesByLocationMapInner: FunctionComponent<SequencesByLocationProps> =
|
|
|
75
77
|
throw error;
|
|
76
78
|
}
|
|
77
79
|
|
|
80
|
+
if (data.tableData.length === 0) {
|
|
81
|
+
return <NoDataDisplay />;
|
|
82
|
+
}
|
|
83
|
+
|
|
78
84
|
return <SequencesByLocationMapTabs data={data} originalComponentProps={props} />;
|
|
79
85
|
};
|
|
80
86
|
|
|
@@ -87,6 +93,8 @@ const SequencesByLocationMapTabs: FunctionComponent<SequencesByLocationMapTabsPr
|
|
|
87
93
|
originalComponentProps,
|
|
88
94
|
data,
|
|
89
95
|
}) => {
|
|
96
|
+
const maintainAspectRatio = getMaintainAspectRatio(originalComponentProps.height);
|
|
97
|
+
|
|
90
98
|
const getTab = (view: SequencesByLocationMapView) => {
|
|
91
99
|
switch (view) {
|
|
92
100
|
case views.map: {
|
|
@@ -105,6 +113,7 @@ const SequencesByLocationMapTabs: FunctionComponent<SequencesByLocationMapTabsPr
|
|
|
105
113
|
offsetX={originalComponentProps.offsetX}
|
|
106
114
|
offsetY={originalComponentProps.offsetY}
|
|
107
115
|
hasTableView={originalComponentProps.views.includes(views.table)}
|
|
116
|
+
maintainAspectRatio={maintainAspectRatio}
|
|
108
117
|
/>
|
|
109
118
|
),
|
|
110
119
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type PropsWithChildren } from 'react';
|
|
2
|
+
|
|
3
|
+
export function AspectRatio({ children, aspectRatio }: PropsWithChildren<{ aspectRatio?: number }>) {
|
|
4
|
+
if (aspectRatio === undefined) {
|
|
5
|
+
return children;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<div class={`w-full relative`} style={{ paddingTop: `${aspectRatio}%` }}>
|
|
10
|
+
<div className='absolute inset-0'>{children}</div>
|
|
11
|
+
</div>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
@@ -13,7 +13,7 @@ import { useQuery } from '../useQuery';
|
|
|
13
13
|
|
|
14
14
|
const statisticsPropsSchema = z.object({
|
|
15
15
|
width: z.string(),
|
|
16
|
-
height: z.string(),
|
|
16
|
+
height: z.string().optional(),
|
|
17
17
|
numeratorFilter: lapisFilterSchema,
|
|
18
18
|
denominatorFilter: lapisFilterSchema,
|
|
19
19
|
});
|
|
@@ -62,17 +62,17 @@ type MetricDataTabsProps = {
|
|
|
62
62
|
const MetricDataTabs: FunctionComponent<MetricDataTabsProps> = ({ data }) => {
|
|
63
63
|
const { count, proportion } = data;
|
|
64
64
|
return (
|
|
65
|
-
<div className='flex flex-col sm:flex-row rounded-md border-2 border-gray-100'>
|
|
65
|
+
<div className='flex flex-col sm:flex-row rounded-md border-2 border-gray-100 min-w-[180px]'>
|
|
66
66
|
<div className='stat'>
|
|
67
67
|
<div className='stat-title'>Sequences</div>
|
|
68
68
|
<div className='stat-value text-2xl sm:text-4xl'>{count.toLocaleString('en-us')}</div>
|
|
69
|
-
<div className='stat-desc'>The total number of sequenced samples</div>
|
|
69
|
+
<div className='stat-desc text-wrap'>The total number of sequenced samples</div>
|
|
70
70
|
</div>
|
|
71
71
|
|
|
72
72
|
<div className='stat'>
|
|
73
73
|
<div className='stat-title'>Overall proportion</div>
|
|
74
74
|
<div className='stat-value text-2xl sm:text-4xl'>{formatProportion(proportion)}</div>
|
|
75
|
-
<div className='stat-desc'>The proportion among all sequenced samples</div>
|
|
75
|
+
<div className='stat-desc text-wrap'>The proportion among all sequenced samples</div>
|
|
76
76
|
</div>
|
|
77
77
|
</div>
|
|
78
78
|
);
|
package/src/preact/{textInput/TextInputChangedEvent.ts → textFilter/TextFilterChangedEvent.ts}
RENAMED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
type LapisTextFilter = Record<string, string | undefined>;
|
|
2
2
|
|
|
3
|
-
export class
|
|
3
|
+
export class TextFilterChangedEvent extends CustomEvent<LapisTextFilter> {
|
|
4
4
|
constructor(detail: LapisTextFilter) {
|
|
5
|
-
super('gs-text-
|
|
5
|
+
super('gs-text-filter-changed', {
|
|
6
6
|
detail,
|
|
7
7
|
bubbles: true,
|
|
8
8
|
composed: true,
|
|
@@ -2,18 +2,18 @@ import { type Meta, type StoryObj } from '@storybook/preact';
|
|
|
2
2
|
import { expect, fireEvent, fn, waitFor, within } from '@storybook/test';
|
|
3
3
|
|
|
4
4
|
import data from './__mockData__/aggregated_hosts.json';
|
|
5
|
-
import {
|
|
5
|
+
import { TextFilter, type TextFilterProps } from './text-filter';
|
|
6
6
|
import { previewHandles } from '../../../.storybook/preview';
|
|
7
7
|
import { AGGREGATED_ENDPOINT, LAPIS_URL } from '../../constants';
|
|
8
8
|
import { LapisUrlContextProvider } from '../LapisUrlContext';
|
|
9
9
|
import { expectInvalidAttributesErrorMessage } from '../shared/stories/expectErrorMessage';
|
|
10
10
|
|
|
11
|
-
const meta: Meta<
|
|
12
|
-
title: 'Input/
|
|
13
|
-
component:
|
|
11
|
+
const meta: Meta<TextFilterProps> = {
|
|
12
|
+
title: 'Input/TextFilter',
|
|
13
|
+
component: TextFilter,
|
|
14
14
|
parameters: {
|
|
15
15
|
actions: {
|
|
16
|
-
handles: ['gs-text-
|
|
16
|
+
handles: ['gs-text-filter-changed', ...previewHandles],
|
|
17
17
|
},
|
|
18
18
|
fetchMock: {
|
|
19
19
|
mocks: [
|
|
@@ -65,10 +65,10 @@ const meta: Meta<TextInputProps> = {
|
|
|
65
65
|
|
|
66
66
|
export default meta;
|
|
67
67
|
|
|
68
|
-
export const Default: StoryObj<
|
|
68
|
+
export const Default: StoryObj<TextFilterProps> = {
|
|
69
69
|
render: (args) => (
|
|
70
70
|
<LapisUrlContextProvider value={LAPIS_URL}>
|
|
71
|
-
<
|
|
71
|
+
<TextFilter {...args} />
|
|
72
72
|
</LapisUrlContextProvider>
|
|
73
73
|
),
|
|
74
74
|
args: {
|
|
@@ -82,7 +82,7 @@ export const Default: StoryObj<TextInputProps> = {
|
|
|
82
82
|
},
|
|
83
83
|
};
|
|
84
84
|
|
|
85
|
-
export const RemoveInitialValue: StoryObj<
|
|
85
|
+
export const RemoveInitialValue: StoryObj<TextFilterProps> = {
|
|
86
86
|
...Default,
|
|
87
87
|
args: {
|
|
88
88
|
...Default.args,
|
|
@@ -93,7 +93,7 @@ export const RemoveInitialValue: StoryObj<TextInputProps> = {
|
|
|
93
93
|
|
|
94
94
|
const changedListenerMock = fn();
|
|
95
95
|
await step('Setup event listener mock', () => {
|
|
96
|
-
canvasElement.addEventListener('gs-text-
|
|
96
|
+
canvasElement.addEventListener('gs-text-filter-changed', changedListenerMock);
|
|
97
97
|
});
|
|
98
98
|
|
|
99
99
|
await waitFor(async () => {
|
|
@@ -115,7 +115,7 @@ export const RemoveInitialValue: StoryObj<TextInputProps> = {
|
|
|
115
115
|
},
|
|
116
116
|
};
|
|
117
117
|
|
|
118
|
-
export const WithNoLapisField: StoryObj<
|
|
118
|
+
export const WithNoLapisField: StoryObj<TextFilterProps> = {
|
|
119
119
|
...Default,
|
|
120
120
|
args: {
|
|
121
121
|
...Default.args,
|
|
@@ -2,7 +2,7 @@ import { type FunctionComponent } from 'preact';
|
|
|
2
2
|
import z from 'zod';
|
|
3
3
|
|
|
4
4
|
import { useLapisUrl } from '../LapisUrlContext';
|
|
5
|
-
import {
|
|
5
|
+
import { TextFilterChangedEvent } from './TextFilterChangedEvent';
|
|
6
6
|
import { fetchStringAutocompleteList } from './fetchStringAutocompleteList';
|
|
7
7
|
import { lapisFilterSchema } from '../../types';
|
|
8
8
|
import { DownshiftCombobox } from '../components/downshift-combobox';
|
|
@@ -17,29 +17,29 @@ const textSelectorPropsSchema = z.object({
|
|
|
17
17
|
placeholderText: z.string().optional(),
|
|
18
18
|
value: z.string().optional(),
|
|
19
19
|
});
|
|
20
|
-
const
|
|
21
|
-
const
|
|
20
|
+
const textFilterInnerPropsSchema = textSelectorPropsSchema.extend({ lapisFilter: lapisFilterSchema });
|
|
21
|
+
const textFilterPropsSchema = textFilterInnerPropsSchema.extend({
|
|
22
22
|
width: z.string(),
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
export type
|
|
26
|
-
export type
|
|
25
|
+
export type TextFilterInnerProps = z.infer<typeof textFilterInnerPropsSchema>;
|
|
26
|
+
export type TextFilterProps = z.infer<typeof textFilterPropsSchema>;
|
|
27
27
|
type TextSelectorProps = z.infer<typeof textSelectorPropsSchema>;
|
|
28
28
|
|
|
29
|
-
export const
|
|
29
|
+
export const TextFilter: FunctionComponent<TextFilterProps> = (props) => {
|
|
30
30
|
const { width, ...innerProps } = props;
|
|
31
31
|
const size = { width, height: '3rem' };
|
|
32
32
|
|
|
33
33
|
return (
|
|
34
|
-
<ErrorBoundary size={size} layout='horizontal' componentProps={props} schema={
|
|
34
|
+
<ErrorBoundary size={size} layout='horizontal' componentProps={props} schema={textFilterPropsSchema}>
|
|
35
35
|
<ResizeContainer size={size}>
|
|
36
|
-
<
|
|
36
|
+
<TextFilterInner {...innerProps} />
|
|
37
37
|
</ResizeContainer>
|
|
38
38
|
</ErrorBoundary>
|
|
39
39
|
);
|
|
40
40
|
};
|
|
41
41
|
|
|
42
|
-
const
|
|
42
|
+
const TextFilterInner: FunctionComponent<TextFilterInnerProps> = ({
|
|
43
43
|
value,
|
|
44
44
|
lapisField,
|
|
45
45
|
placeholderText,
|
|
@@ -88,7 +88,7 @@ const TextSelector = ({
|
|
|
88
88
|
value={initialSelectedItem}
|
|
89
89
|
filterItemsByInputValue={filterByInputValue}
|
|
90
90
|
createEvent={(item: SelectItem | null) =>
|
|
91
|
-
new
|
|
91
|
+
new TextFilterChangedEvent({ [lapisField]: item?.value ?? undefined })
|
|
92
92
|
}
|
|
93
93
|
itemToString={(item: SelectItem | undefined | null) => item?.value ?? ''}
|
|
94
94
|
placeholderText={placeholderText}
|
|
@@ -2,7 +2,7 @@ import type { FeatureCollection, GeometryObject } from 'geojson';
|
|
|
2
2
|
import { describe, expect, test } from 'vitest';
|
|
3
3
|
|
|
4
4
|
import { computeMapLocationData } from './computeMapLocationData';
|
|
5
|
-
import type { GeoJsonFeatureProperties } from '../preact/
|
|
5
|
+
import type { GeoJsonFeatureProperties } from '../preact/sequencesByLocation/loadMapSource';
|
|
6
6
|
|
|
7
7
|
const lapisLocationField = 'locationField';
|
|
8
8
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Feature, FeatureCollection, GeometryObject } from 'geojson';
|
|
2
2
|
|
|
3
3
|
import type { AggregateData } from './queryAggregateData';
|
|
4
|
-
import type { GeoJsonFeatureProperties } from '../preact/
|
|
4
|
+
import type { GeoJsonFeatureProperties } from '../preact/sequencesByLocation/loadMapSource';
|
|
5
5
|
|
|
6
6
|
export type FeatureData = { proportion: number; count: number };
|
|
7
7
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { computeMapLocationData } from './computeMapLocationData';
|
|
2
2
|
import { queryAggregateData } from './queryAggregateData';
|
|
3
|
-
import { loadMapSource, type MapSource } from '../preact/
|
|
3
|
+
import { loadMapSource, type MapSource } from '../preact/sequencesByLocation/loadMapSource';
|
|
4
4
|
import type { LapisFilter } from '../types';
|
|
5
5
|
|
|
6
6
|
export async function querySequencesByLocationData(
|
package/src/utilEntrypoint.ts
CHANGED
|
@@ -3,7 +3,7 @@ export {
|
|
|
3
3
|
type DateRangeSelectOption,
|
|
4
4
|
dateRangeOptionPresets,
|
|
5
5
|
DateRangeOptionChangedEvent,
|
|
6
|
-
} from './preact/
|
|
6
|
+
} from './preact/dateRangeFilter/dateRangeOption';
|
|
7
7
|
|
|
8
8
|
export {
|
|
9
9
|
type NamedLapisFilter,
|
|
@@ -27,7 +27,7 @@ export type {
|
|
|
27
27
|
RelativeGrowthAdvantageProps,
|
|
28
28
|
} from './preact/relativeGrowthAdvantage/relative-growth-advantage';
|
|
29
29
|
export type { StatisticsProps } from './preact/statistic/statistics';
|
|
30
|
-
export type { MapSource } from './preact/
|
|
30
|
+
export type { MapSource } from './preact/sequencesByLocation/loadMapSource';
|
|
31
31
|
|
|
32
32
|
export type { ConfidenceIntervalMethod } from './preact/shared/charts/confideceInterval';
|
|
33
33
|
|
|
@@ -35,4 +35,4 @@ export type { AxisMax, YAxisMaxConfig } from './preact/shared/charts/getYAxisMax
|
|
|
35
35
|
|
|
36
36
|
export { LocationChangedEvent } from './preact/locationFilter/LocationChangedEvent';
|
|
37
37
|
export { LineageFilterChangedEvent } from './preact/lineageFilter/LineageFilterChangedEvent';
|
|
38
|
-
export {
|
|
38
|
+
export { TextFilterChangedEvent } from './preact/textFilter/TextFilterChangedEvent';
|
|
@@ -5,7 +5,10 @@ import { Meta } from '@storybook/blocks';
|
|
|
5
5
|
# Size of components
|
|
6
6
|
|
|
7
7
|
All visualization and input components can be provided with a width prop (or height, when applicable) to control the size of the component.
|
|
8
|
-
If not provided, the default
|
|
8
|
+
If the width is not provided, the default is used. In most cases, the default is a width of '100%' of its parent container.
|
|
9
|
+
If the height is not set, the component takes up as much space as needed in the vertical direction.
|
|
10
|
+
For table views, this includes showing the complete table.
|
|
11
|
+
For diagrams, a fixed aspect ratio of 2:1 (width/height) is used.
|
|
9
12
|
|
|
10
13
|
Both width and height can be set using the css units (e.g. 'px', 'em', 'rem', '%', 'vh', 'vw', etc.).
|
|
11
14
|
By using '%', the size of the component can be controlled by the parent component.
|
|
@@ -5,27 +5,27 @@ import { html } from 'lit';
|
|
|
5
5
|
import { withComponentDocs } from '../../../.storybook/ComponentDocsBlock';
|
|
6
6
|
import { previewHandles } from '../../../.storybook/preview';
|
|
7
7
|
import { LAPIS_URL } from '../../constants';
|
|
8
|
-
import { type
|
|
9
|
-
import './gs-date-range-
|
|
8
|
+
import { type DateRangeFilterProps } from '../../preact/dateRangeFilter/date-range-filter';
|
|
9
|
+
import './gs-date-range-filter';
|
|
10
10
|
import '../gs-app';
|
|
11
|
-
import { toYYYYMMDD } from '../../preact/
|
|
12
|
-
import { dateRangeOptionPresets } from '../../preact/
|
|
11
|
+
import { toYYYYMMDD } from '../../preact/dateRangeFilter/dateConversion';
|
|
12
|
+
import { dateRangeOptionPresets } from '../../preact/dateRangeFilter/dateRangeOption';
|
|
13
13
|
import { withinShadowRoot } from '../withinShadowRoot.story';
|
|
14
14
|
|
|
15
15
|
const codeExample = String.raw`
|
|
16
|
-
<gs-date-range-
|
|
16
|
+
<gs-date-range-filter
|
|
17
17
|
dateRangeOptions='[{ "label": "Year 2021", "dateFrom": "2021-01-01", "dateTo": "2021-12-31" }]'
|
|
18
18
|
earliestDate="1970-01-01"
|
|
19
19
|
value="Year 2021"
|
|
20
20
|
width="100%"
|
|
21
21
|
lapisDateField="myDateColumn"
|
|
22
|
-
></gs-date-range-
|
|
22
|
+
></gs-date-range-filter>`;
|
|
23
23
|
|
|
24
24
|
const customDateRange = { label: 'CustomDateRange', dateFrom: '2021-01-01', dateTo: '2021-12-31' };
|
|
25
25
|
|
|
26
|
-
const meta: Meta<Required<
|
|
27
|
-
title: 'Input/
|
|
28
|
-
component: 'gs-date-range-
|
|
26
|
+
const meta: Meta<Required<DateRangeFilterProps>> = {
|
|
27
|
+
title: 'Input/DateRangeFilter',
|
|
28
|
+
component: 'gs-date-range-filter',
|
|
29
29
|
parameters: withComponentDocs({
|
|
30
30
|
actions: {
|
|
31
31
|
handles: ['gs-date-range-filter-changed', 'gs-date-range-option-changed', ...previewHandles],
|
|
@@ -65,6 +65,7 @@ const meta: Meta<Required<DateRangeSelectorProps>> = {
|
|
|
65
65
|
dateRangeOptionPresets.lastMonth,
|
|
66
66
|
dateRangeOptionPresets.last3Months,
|
|
67
67
|
dateRangeOptionPresets.allTimes,
|
|
68
|
+
{ label: '2021', dateFrom: '2021-01-01', dateTo: '2021-12-31' },
|
|
68
69
|
customDateRange,
|
|
69
70
|
],
|
|
70
71
|
earliestDate: '1970-01-01',
|
|
@@ -77,25 +78,69 @@ const meta: Meta<Required<DateRangeSelectorProps>> = {
|
|
|
77
78
|
|
|
78
79
|
export default meta;
|
|
79
80
|
|
|
80
|
-
export const Default: StoryObj<Required<
|
|
81
|
+
export const Default: StoryObj<Required<DateRangeFilterProps>> = {
|
|
81
82
|
render: (args) =>
|
|
82
83
|
html` <gs-app lapis="${LAPIS_URL}">
|
|
83
84
|
<div class="max-w-screen-lg">
|
|
84
|
-
<gs-date-range-
|
|
85
|
+
<gs-date-range-filter
|
|
85
86
|
.dateRangeOptions=${args.dateRangeOptions}
|
|
86
87
|
.earliestDate=${args.earliestDate}
|
|
87
88
|
.value=${args.value}
|
|
88
89
|
.width=${args.width}
|
|
89
90
|
.lapisDateField=${args.lapisDateField}
|
|
90
|
-
></gs-date-range-
|
|
91
|
+
></gs-date-range-filter>
|
|
91
92
|
</div>
|
|
92
93
|
</gs-app>`,
|
|
93
94
|
};
|
|
94
95
|
|
|
95
|
-
export const
|
|
96
|
+
export const TestRenderAttributesInHtmlInsteadOfUsingPropertyExpression: StoryObj<Required<DateRangeFilterProps>> = {
|
|
97
|
+
render: (args) =>
|
|
98
|
+
html` <gs-app lapis="${LAPIS_URL}">
|
|
99
|
+
<div class="max-w-screen-lg">
|
|
100
|
+
<gs-date-range-filter
|
|
101
|
+
.dateRangeOptions=${args.dateRangeOptions}
|
|
102
|
+
earliestDate="${args.earliestDate}"
|
|
103
|
+
value="${args.value}"
|
|
104
|
+
width="${args.width}"
|
|
105
|
+
lapisDateField="${args.lapisDateField}"
|
|
106
|
+
></gs-date-range-filter>
|
|
107
|
+
</div>
|
|
108
|
+
</gs-app>`,
|
|
109
|
+
play: async ({ canvasElement }) => {
|
|
110
|
+
const canvas = await withinShadowRoot(canvasElement, 'gs-date-range-filter');
|
|
111
|
+
|
|
112
|
+
await waitFor(async () => {
|
|
113
|
+
await expect(selectField(canvas)).toHaveValue('Last month');
|
|
114
|
+
});
|
|
115
|
+
},
|
|
116
|
+
argTypes: {
|
|
117
|
+
value: {
|
|
118
|
+
control: {
|
|
119
|
+
type: 'text',
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export const TestSettingANumericValueIsTreatedAsString: StoryObj<Required<DateRangeFilterProps>> = {
|
|
126
|
+
...TestRenderAttributesInHtmlInsteadOfUsingPropertyExpression,
|
|
127
|
+
args: {
|
|
128
|
+
...TestRenderAttributesInHtmlInsteadOfUsingPropertyExpression.args,
|
|
129
|
+
value: '2021',
|
|
130
|
+
},
|
|
131
|
+
play: async ({ canvasElement }) => {
|
|
132
|
+
const canvas = await withinShadowRoot(canvasElement, 'gs-date-range-filter');
|
|
133
|
+
|
|
134
|
+
await waitFor(async () => {
|
|
135
|
+
await expect(selectField(canvas)).toHaveValue('2021');
|
|
136
|
+
});
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export const FiresEvents: StoryObj<Required<DateRangeFilterProps>> = {
|
|
96
141
|
...Default,
|
|
97
142
|
play: async ({ canvasElement, step }) => {
|
|
98
|
-
const canvas = await withinShadowRoot(canvasElement, 'gs-date-range-
|
|
143
|
+
const canvas = await withinShadowRoot(canvasElement, 'gs-date-range-filter');
|
|
99
144
|
|
|
100
145
|
const filterChangedListenerMock = fn();
|
|
101
146
|
const optionChangedListenerMock = fn();
|