@genspectrum/dashboard-components 1.13.0 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/custom-elements.json +393 -2
  2. package/dist/components.d.ts +126 -9
  3. package/dist/components.js +701 -163
  4. package/dist/components.js.map +1 -1
  5. package/dist/util.d.ts +146 -11
  6. package/package.json +1 -1
  7. package/src/lapisApi/lapisTypes.ts +1 -1
  8. package/src/preact/queriesOverTime/__mockData__/defaultMockData/queriesOverTime.json +32 -0
  9. package/src/preact/queriesOverTime/__mockData__/manyQueries.json +128 -0
  10. package/src/preact/queriesOverTime/__mockData__/request1800s.json +16 -0
  11. package/src/preact/queriesOverTime/__mockData__/withGaps.json +52 -0
  12. package/src/preact/queriesOverTime/getFilteredQueriesOverTimeData.ts +85 -0
  13. package/src/preact/queriesOverTime/queries-over-time-filter.tsx +25 -0
  14. package/src/preact/queriesOverTime/queries-over-time-grid-tooltip.stories.tsx +134 -0
  15. package/src/preact/queriesOverTime/queries-over-time-grid-tooltip.tsx +123 -0
  16. package/src/preact/queriesOverTime/queries-over-time.stories.tsx +481 -0
  17. package/src/preact/queriesOverTime/queries-over-time.tsx +304 -0
  18. package/src/utilEntrypoint.ts +1 -0
  19. package/src/web-components/visualization/gs-mutations-over-time.spec-d.ts +3 -0
  20. package/src/web-components/visualization/gs-mutations-over-time.tsx +1 -1
  21. package/src/web-components/visualization/gs-queries-over-time.spec-d.ts +38 -0
  22. package/src/web-components/visualization/gs-queries-over-time.stories.ts +288 -0
  23. package/src/web-components/visualization/gs-queries-over-time.tsx +154 -0
  24. package/src/web-components/visualization/index.ts +1 -0
  25. package/standalone-bundle/dashboard-components.js +8509 -8068
  26. package/standalone-bundle/dashboard-components.js.map +1 -1
@@ -0,0 +1,304 @@
1
+ import { type FunctionComponent } from 'preact';
2
+ import { type Dispatch, type StateUpdater, useMemo, useState, useEffect, useLayoutEffect, useRef } from 'preact/hooks';
3
+ import z from 'zod';
4
+
5
+ import { getFilteredQueryOverTimeData, type QueryFilter } from './getFilteredQueriesOverTimeData';
6
+ import { QueriesOverTimeFilter } from './queries-over-time-filter';
7
+ import { QueriesOverTimeGridTooltip } from './queries-over-time-grid-tooltip';
8
+ import { type ProportionValue, getProportion } from '../../query/queryMutationsOverTime';
9
+ import { queryQueriesOverTimeData } from '../../query/queryQueriesOverTime';
10
+ import { lapisFilterSchema, temporalGranularitySchema, views } from '../../types';
11
+ import { type Temporal, toTemporalClass } from '../../utils/temporalClass';
12
+ import { useDispatchFinishedLoadingEvent } from '../../utils/useDispatchFinishedLoadingEvent';
13
+ import { useLapisUrl } from '../LapisUrlContext';
14
+ import { type ColorScale } from '../components/color-scale-selector';
15
+ import { ColorScaleSelectorDropdown } from '../components/color-scale-selector-dropdown';
16
+ import { CsvDownloadButton } from '../components/csv-download-button';
17
+ import { ErrorBoundary } from '../components/error-boundary';
18
+ import FeaturesOverTimeGrid, { type FeatureRenderer, customColumnSchema } from '../components/features-over-time-grid';
19
+ import { Fullscreen } from '../components/fullscreen';
20
+ import Info, { InfoComponentCode, InfoHeadline1, InfoParagraph } from '../components/info';
21
+ import { LoadingDisplay } from '../components/loading-display';
22
+ import { NoDataDisplay } from '../components/no-data-display';
23
+ import type { ProportionInterval } from '../components/proportion-selector';
24
+ import { ProportionSelectorDropdown } from '../components/proportion-selector-dropdown';
25
+ import { ResizeContainer } from '../components/resize-container';
26
+ import Tabs from '../components/tabs';
27
+ import { pageSizesSchema } from '../shared/tanstackTable/pagination';
28
+ import { PageSizeContextProvider } from '../shared/tanstackTable/pagination-context';
29
+ import { useQuery } from '../useQuery';
30
+
31
+ const queriesOverTimeViewSchema = z.literal(views.grid);
32
+ export type QueriesOverTimeView = z.infer<typeof queriesOverTimeViewSchema>;
33
+
34
+ const meanProportionIntervalSchema = z.object({
35
+ min: z.number().min(0).max(1),
36
+ max: z.number().min(0).max(1),
37
+ });
38
+ export type MeanProportionInterval = z.infer<typeof meanProportionIntervalSchema>;
39
+
40
+ const queriesOverTimeSchema = z.object({
41
+ lapisFilter: lapisFilterSchema,
42
+ queries: z
43
+ .array(
44
+ z.object({
45
+ displayLabel: z.string(),
46
+ countQuery: z.string(),
47
+ coverageQuery: z.string(),
48
+ }),
49
+ )
50
+ .min(1),
51
+ views: z.array(queriesOverTimeViewSchema),
52
+ granularity: temporalGranularitySchema,
53
+ lapisDateField: z.string().min(1),
54
+ initialMeanProportionInterval: meanProportionIntervalSchema,
55
+ hideGaps: z.boolean().optional(),
56
+ width: z.string(),
57
+ height: z.string().optional(),
58
+ pageSizes: pageSizesSchema,
59
+ customColumns: z.array(customColumnSchema).optional(),
60
+ });
61
+ export type QueriesOverTimeProps = z.infer<typeof queriesOverTimeSchema>;
62
+
63
+ export const QueriesOverTime: FunctionComponent<QueriesOverTimeProps> = (componentProps) => {
64
+ const { width, height } = componentProps;
65
+ const size = { height, width };
66
+
67
+ return (
68
+ <ErrorBoundary size={size} schema={queriesOverTimeSchema} componentProps={componentProps}>
69
+ <ResizeContainer size={size}>
70
+ <QueriesOverTimeInner {...componentProps} />
71
+ </ResizeContainer>
72
+ </ErrorBoundary>
73
+ );
74
+ };
75
+
76
+ export const QueriesOverTimeInner: FunctionComponent<QueriesOverTimeProps> = ({ ...componentProps }) => {
77
+ const lapis = useLapisUrl();
78
+ const { lapisFilter, queries, granularity, lapisDateField } = componentProps;
79
+
80
+ const { data, error, isLoading } = useQuery(
81
+ () => queryQueriesOverTimeData(lapisFilter, queries, lapis, lapisDateField, granularity),
82
+ [granularity, lapis, lapisDateField, lapisFilter, queries],
83
+ );
84
+
85
+ if (isLoading) {
86
+ return <LoadingDisplay />;
87
+ }
88
+
89
+ if (error !== null) {
90
+ throw error;
91
+ }
92
+
93
+ const { queryOverTimeData } = data;
94
+
95
+ // Check if there's any data
96
+ if (queryOverTimeData.keysFirstAxis.size === 0) {
97
+ return <NoDataDisplay />;
98
+ }
99
+
100
+ return <QueriesOverTimeTabs queryOverTimeData={queryOverTimeData} originalComponentProps={componentProps} />;
101
+ };
102
+
103
+ type QueriesOverTimeTabsProps = {
104
+ queryOverTimeData: Awaited<ReturnType<typeof queryQueriesOverTimeData>>['queryOverTimeData'];
105
+ originalComponentProps: QueriesOverTimeProps;
106
+ };
107
+
108
+ const QueriesOverTimeTabs: FunctionComponent<QueriesOverTimeTabsProps> = ({
109
+ queryOverTimeData,
110
+ originalComponentProps,
111
+ }) => {
112
+ const tabsRef = useDispatchFinishedLoadingEvent();
113
+ const tooltipPortalTargetRef = useRef<HTMLDivElement>(null);
114
+ const [tooltipPortalTarget, setTooltipPortalTarget] = useState<HTMLDivElement | null>(null);
115
+
116
+ useLayoutEffect(() => {
117
+ setTooltipPortalTarget(tooltipPortalTargetRef.current);
118
+ }, []);
119
+
120
+ const [queryFilterValue, setQueryFilterValue] = useState<QueryFilter>({
121
+ textFilter: '',
122
+ });
123
+
124
+ const [proportionInterval, setProportionInterval] = useState(originalComponentProps.initialMeanProportionInterval);
125
+ const [colorScale, setColorScale] = useState<ColorScale>({ min: 0, max: 1, color: 'indigo' });
126
+ const [hideGaps, setHideGaps] = useState<boolean>(originalComponentProps.hideGaps ?? false);
127
+
128
+ useEffect(() => setHideGaps(originalComponentProps.hideGaps ?? false), [originalComponentProps.hideGaps]);
129
+
130
+ const filteredData = useMemo(() => {
131
+ return getFilteredQueryOverTimeData({
132
+ data: queryOverTimeData,
133
+ proportionInterval,
134
+ hideGaps,
135
+ queryFilterValue,
136
+ });
137
+ }, [queryOverTimeData, proportionInterval, hideGaps, queryFilterValue]);
138
+
139
+ const queryRenderer = useMemo<FeatureRenderer<string>>(
140
+ () => ({
141
+ asString: (value: string) => value,
142
+ renderRowLabel: (value: string) => (
143
+ <div className='text-center'>
144
+ <span>{value}</span>
145
+ </div>
146
+ ),
147
+ renderTooltip: (value: string, temporal: Temporal, proportionValue: ProportionValue) => (
148
+ <QueriesOverTimeGridTooltip query={value} date={temporal} value={proportionValue} />
149
+ ),
150
+ }),
151
+ [],
152
+ );
153
+
154
+ const getTab = (view: QueriesOverTimeView) => {
155
+ switch (view) {
156
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- for extensibility
157
+ case 'grid':
158
+ return {
159
+ title: 'Grid',
160
+ content: (
161
+ <FeaturesOverTimeGrid
162
+ rowLabelHeader='Query'
163
+ data={filteredData}
164
+ colorScale={colorScale}
165
+ pageSizes={originalComponentProps.pageSizes}
166
+ customColumns={originalComponentProps.customColumns}
167
+ featureRenderer={queryRenderer}
168
+ tooltipPortalTarget={tooltipPortalTarget}
169
+ />
170
+ ),
171
+ };
172
+ }
173
+ };
174
+
175
+ const tabs = originalComponentProps.views.map((view) => getTab(view));
176
+
177
+ const toolbar = (activeTab: string) => (
178
+ <Toolbar
179
+ activeTab={activeTab}
180
+ proportionInterval={proportionInterval}
181
+ setProportionInterval={setProportionInterval}
182
+ hideGaps={hideGaps}
183
+ setHideGaps={setHideGaps}
184
+ filteredData={filteredData}
185
+ colorScale={colorScale}
186
+ setColorScale={setColorScale}
187
+ originalComponentProps={originalComponentProps}
188
+ setFilterValue={setQueryFilterValue}
189
+ queryFilterValue={queryFilterValue}
190
+ />
191
+ );
192
+
193
+ return (
194
+ <div ref={tooltipPortalTargetRef}>
195
+ <PageSizeContextProvider pageSizes={originalComponentProps.pageSizes}>
196
+ <Tabs ref={tabsRef} tabs={tabs} toolbar={toolbar} />
197
+ </PageSizeContextProvider>
198
+ </div>
199
+ );
200
+ };
201
+
202
+ type ToolbarProps = {
203
+ activeTab: string;
204
+ proportionInterval: ProportionInterval;
205
+ setProportionInterval: Dispatch<StateUpdater<ProportionInterval>>;
206
+ hideGaps: boolean;
207
+ setHideGaps: Dispatch<StateUpdater<boolean>>;
208
+ filteredData: ReturnType<typeof getFilteredQueryOverTimeData>;
209
+ colorScale: ColorScale;
210
+ setColorScale: Dispatch<StateUpdater<ColorScale>>;
211
+ originalComponentProps: QueriesOverTimeProps;
212
+ queryFilterValue: QueryFilter;
213
+ setFilterValue: Dispatch<StateUpdater<QueryFilter>>;
214
+ };
215
+
216
+ const Toolbar: FunctionComponent<ToolbarProps> = ({
217
+ activeTab,
218
+ proportionInterval,
219
+ setProportionInterval,
220
+ hideGaps,
221
+ setHideGaps,
222
+ filteredData,
223
+ colorScale,
224
+ setColorScale,
225
+ originalComponentProps,
226
+ setFilterValue,
227
+ queryFilterValue,
228
+ }) => {
229
+ return (
230
+ <>
231
+ <QueriesOverTimeFilter setFilterValue={setFilterValue} value={queryFilterValue} />
232
+ <ProportionSelectorDropdown
233
+ proportionInterval={proportionInterval}
234
+ setMinProportion={(min) => setProportionInterval((prev) => ({ ...prev, min }))}
235
+ setMaxProportion={(max) => setProportionInterval((prev) => ({ ...prev, max }))}
236
+ labelPrefix='Mean proportion'
237
+ />
238
+ <button
239
+ className='btn btn-xs w-24'
240
+ onClick={() => setHideGaps((s) => !s)}
241
+ title={
242
+ hideGaps
243
+ ? 'Date ranges that do not contain data are excluded from the table'
244
+ : 'Exclude date ranges without data from the table'
245
+ }
246
+ >
247
+ {hideGaps ? 'Gaps hidden' : 'Hide gaps'}
248
+ </button>
249
+ {activeTab === 'Grid' && (
250
+ <ColorScaleSelectorDropdown colorScale={colorScale} setColorScale={setColorScale} />
251
+ )}
252
+ <CsvDownloadButton
253
+ className='btn btn-xs'
254
+ getData={() => getDownloadData(filteredData)}
255
+ filename='queries_over_time.csv'
256
+ />
257
+ <QueriesOverTimeInfo originalComponentProps={originalComponentProps} />
258
+ <Fullscreen />
259
+ </>
260
+ );
261
+ };
262
+
263
+ type QueriesOverTimeInfoProps = {
264
+ originalComponentProps: QueriesOverTimeProps;
265
+ };
266
+
267
+ const QueriesOverTimeInfo: FunctionComponent<QueriesOverTimeInfoProps> = ({ originalComponentProps }) => {
268
+ const lapis = useLapisUrl();
269
+ return (
270
+ <Info>
271
+ <InfoHeadline1>Queries over time</InfoHeadline1>
272
+ <InfoParagraph>
273
+ This component displays the proportions of custom queries per {originalComponentProps.granularity}. Each
274
+ query consists of a count query (what to count) and a coverage query (what to use as the denominator).
275
+ In the toolbar, you can filter queries by text and configure which queries are displayed by applying a
276
+ filter based on the mean proportion of the query's occurrence over the entire time range.
277
+ </InfoParagraph>
278
+ <InfoParagraph>
279
+ The grid cells have a tooltip that will show more detailed information. It shows the count of samples
280
+ that match the count query and the count of samples that match the coverage query in this timeframe. It
281
+ also shows the total count of samples in this timeframe.
282
+ </InfoParagraph>
283
+ <InfoComponentCode componentName='queries-over-time' params={originalComponentProps} lapisUrl={lapis} />
284
+ </Info>
285
+ );
286
+ };
287
+
288
+ function getDownloadData(filteredData: ReturnType<typeof getFilteredQueryOverTimeData>) {
289
+ const dates = filteredData.getSecondAxisKeys().map((date) => toTemporalClass(date));
290
+
291
+ return filteredData.getFirstAxisKeys().map((query) => {
292
+ return dates.reduce(
293
+ (accumulated, date) => {
294
+ const value = filteredData.get(query, date);
295
+ const proportion = getProportion(value ?? null) ?? '';
296
+ return {
297
+ ...accumulated,
298
+ [date.dateString]: proportion,
299
+ };
300
+ },
301
+ { query },
302
+ );
303
+ });
304
+ }
@@ -24,6 +24,7 @@ export type {
24
24
  NumberSequencesOverTimeProps,
25
25
  } from './preact/numberSequencesOverTime/number-sequences-over-time';
26
26
  export type { PrevalenceOverTimeView, PrevalenceOverTimeProps } from './preact/prevalenceOverTime/prevalence-over-time';
27
+ export type { QueriesOverTimeView, QueriesOverTimeProps } from './preact/queriesOverTime/queries-over-time';
27
28
  export type {
28
29
  RelativeGrowthAdvantageView,
29
30
  RelativeGrowthAdvantageProps,
@@ -32,6 +32,9 @@ describe('gs-mutations-over-time', () => {
32
32
  expectTypeOf<typeof MutationsOverTimeComponent.prototype.initialMeanProportionInterval>().toEqualTypeOf<
33
33
  MutationsOverTimeProps['initialMeanProportionInterval']
34
34
  >();
35
+ expectTypeOf<typeof MutationsOverTimeComponent.prototype.hideGaps>().toEqualTypeOf<
36
+ MutationsOverTimeProps['hideGaps']
37
+ >();
35
38
  expectTypeOf<typeof MutationsOverTimeComponent.prototype.pageSizes>().toEqualTypeOf<
36
39
  MutationsOverTimeProps['pageSizes']
37
40
  >();
@@ -120,7 +120,7 @@ export class MutationsOverTimeComponent extends PreactLitAdapterWithGridJsStyles
120
120
  * Can be switched with a button in the toolbar.
121
121
  */
122
122
  @property({ type: Boolean })
123
- hideGaps: boolean = false;
123
+ hideGaps: boolean | undefined = false;
124
124
 
125
125
  /**
126
126
  * The number of rows per page, which can be selected by the user.
@@ -0,0 +1,38 @@
1
+ import { describe, expectTypeOf, it } from 'vitest';
2
+
3
+ import { type QueriesOverTimeComponent } from './gs-queries-over-time';
4
+ import { type QueriesOverTimeProps } from '../../preact/queriesOverTime/queries-over-time';
5
+
6
+ describe('gs-queries-over-time', () => {
7
+ it('types of preact and lit component should match', () => {
8
+ expectTypeOf<typeof QueriesOverTimeComponent.prototype.lapisFilter>().toEqualTypeOf<
9
+ QueriesOverTimeProps['lapisFilter']
10
+ >();
11
+ expectTypeOf<typeof QueriesOverTimeComponent.prototype.queries>().toEqualTypeOf<
12
+ QueriesOverTimeProps['queries']
13
+ >();
14
+ expectTypeOf<typeof QueriesOverTimeComponent.prototype.views>().toEqualTypeOf<QueriesOverTimeProps['views']>();
15
+ expectTypeOf<typeof QueriesOverTimeComponent.prototype.width>().toEqualTypeOf<QueriesOverTimeProps['width']>();
16
+ expectTypeOf<typeof QueriesOverTimeComponent.prototype.height>().toEqualTypeOf<
17
+ QueriesOverTimeProps['height']
18
+ >();
19
+ expectTypeOf<typeof QueriesOverTimeComponent.prototype.granularity>().toEqualTypeOf<
20
+ QueriesOverTimeProps['granularity']
21
+ >();
22
+ expectTypeOf<typeof QueriesOverTimeComponent.prototype.lapisDateField>().toEqualTypeOf<
23
+ QueriesOverTimeProps['lapisDateField']
24
+ >();
25
+ expectTypeOf<typeof QueriesOverTimeComponent.prototype.initialMeanProportionInterval>().toEqualTypeOf<
26
+ QueriesOverTimeProps['initialMeanProportionInterval']
27
+ >();
28
+ expectTypeOf<typeof QueriesOverTimeComponent.prototype.hideGaps>().toEqualTypeOf<
29
+ QueriesOverTimeProps['hideGaps']
30
+ >();
31
+ expectTypeOf<typeof QueriesOverTimeComponent.prototype.pageSizes>().toEqualTypeOf<
32
+ QueriesOverTimeProps['pageSizes']
33
+ >();
34
+ expectTypeOf<typeof QueriesOverTimeComponent.prototype.customColumns>().toEqualTypeOf<
35
+ QueriesOverTimeProps['customColumns']
36
+ >();
37
+ });
38
+ });
@@ -0,0 +1,288 @@
1
+ import type { Meta, StoryObj } from '@storybook/web-components';
2
+ import { html } from 'lit';
3
+
4
+ import './gs-queries-over-time';
5
+ import '../gs-app';
6
+ import { withComponentDocs } from '../../../.storybook/ComponentDocsBlock';
7
+ import { LAPIS_URL } from '../../constants';
8
+ import mockDefaultQueriesOverTime from '../../preact/queriesOverTime/__mockData__/defaultMockData/queriesOverTime.json';
9
+ import mockWithGapsQueriesOverTime from '../../preact/queriesOverTime/__mockData__/withGaps.json';
10
+ import { type QueriesOverTimeProps } from '../../preact/queriesOverTime/queries-over-time';
11
+
12
+ const codeExample = String.raw`
13
+ <gs-queries-over-time
14
+ lapisFilter='{ "pangoLineage": "JN.1*", "dateFrom": "2024-01-15", "dateTo": "2024-04-30" }'
15
+ queries='[
16
+ {
17
+ "displayLabel": "S:F456L (single mutation)",
18
+ "countQuery": "S:456L",
19
+ "coverageQuery": "!S:456N"
20
+ },
21
+ {
22
+ "displayLabel": "R346T + F456L (combination)",
23
+ "countQuery": "S:346T & S:456L",
24
+ "coverageQuery": "!S:346N & !S:456N"
25
+ }
26
+ ]'
27
+ views='["grid"]'
28
+ width='100%'
29
+ height='700px'
30
+ granularity="month"
31
+ lapisDateField="date"
32
+ pageSizes='[5,10]'
33
+ ></gs-queries-over-time>`;
34
+
35
+ const meta: Meta<Required<QueriesOverTimeProps>> = {
36
+ title: 'Visualization/Queries over time',
37
+ component: 'gs-queries-over-time',
38
+ argTypes: {
39
+ lapisFilter: { control: 'object' },
40
+ queries: { control: 'object' },
41
+ views: {
42
+ options: ['grid'],
43
+ control: { type: 'check' },
44
+ },
45
+ width: { control: 'text' },
46
+ height: { control: 'text' },
47
+ granularity: {
48
+ options: ['day', 'week', 'month', 'year'],
49
+ control: { type: 'radio' },
50
+ },
51
+ lapisDateField: { control: 'text' },
52
+ initialMeanProportionInterval: { control: 'object' },
53
+ hideGaps: { control: 'boolean' },
54
+ pageSizes: { control: 'object' },
55
+ customColumns: { control: 'object' },
56
+ },
57
+ args: {
58
+ lapisFilter: { pangoLineage: 'JN.1*', dateFrom: '2024-01-15', dateTo: '2024-04-30' },
59
+ queries: [
60
+ {
61
+ displayLabel: 'S:F456L (single mutation)',
62
+ countQuery: 'S:456L',
63
+ coverageQuery: '!S:456N',
64
+ },
65
+ {
66
+ displayLabel: 'R346T + F456L (combination)',
67
+ countQuery: 'S:346T & S:456L',
68
+ coverageQuery: '!S:346N & !S:456N',
69
+ },
70
+ {
71
+ displayLabel: 'C22916T or T22917G (nucleotide OR)',
72
+ countQuery: 'C22916T | T22917G',
73
+ coverageQuery: '!22916N & !22917N',
74
+ },
75
+ ],
76
+ views: ['grid'],
77
+ width: '100%',
78
+ granularity: 'month',
79
+ lapisDateField: 'date',
80
+ initialMeanProportionInterval: { min: 0, max: 1 },
81
+ hideGaps: false,
82
+ pageSizes: [10, 20, 30, 40, 50],
83
+ },
84
+ parameters: withComponentDocs({
85
+ componentDocs: {
86
+ opensShadowDom: true,
87
+ expectsChildren: false,
88
+ codeExample,
89
+ },
90
+ fetchMock: {
91
+ mocks: [
92
+ {
93
+ matcher: {
94
+ url: `${LAPIS_URL}/component/queriesOverTime`,
95
+ body: {
96
+ filters: {
97
+ pangoLineage: 'JN.1*',
98
+ dateFrom: '2024-01-15',
99
+ dateTo: '2024-04-30',
100
+ },
101
+ queries: [
102
+ {
103
+ displayLabel: 'S:F456L (single mutation)',
104
+ countQuery: 'S:456L',
105
+ coverageQuery: '!S:456N',
106
+ },
107
+ {
108
+ displayLabel: 'R346T + F456L (combination)',
109
+ countQuery: 'S:346T & S:456L',
110
+ coverageQuery: '!S:346N & !S:456N',
111
+ },
112
+ {
113
+ displayLabel: 'C22916T or T22917G (nucleotide OR)',
114
+ countQuery: 'C22916T | T22917G',
115
+ coverageQuery: '!22916N & !22917N',
116
+ },
117
+ ],
118
+ dateRanges: [
119
+ { dateFrom: '2024-01-01', dateTo: '2024-01-31' },
120
+ { dateFrom: '2024-02-01', dateTo: '2024-02-29' },
121
+ { dateFrom: '2024-03-01', dateTo: '2024-03-31' },
122
+ { dateFrom: '2024-04-01', dateTo: '2024-04-30' },
123
+ ],
124
+ dateField: 'date',
125
+ },
126
+ matchPartialBody: true,
127
+ response: {
128
+ status: 200,
129
+ body: mockDefaultQueriesOverTime,
130
+ },
131
+ },
132
+ },
133
+ ],
134
+ },
135
+ }),
136
+ tags: ['autodocs'],
137
+ };
138
+
139
+ export default meta;
140
+
141
+ const Template: StoryObj<Required<QueriesOverTimeProps>> = {
142
+ render: (args) => html`
143
+ <gs-app lapis="${LAPIS_URL}">
144
+ <gs-queries-over-time
145
+ .lapisFilter=${args.lapisFilter}
146
+ .queries=${args.queries}
147
+ .views=${args.views}
148
+ .width=${args.width}
149
+ .height=${args.height}
150
+ .granularity=${args.granularity}
151
+ .lapisDateField=${args.lapisDateField}
152
+ .initialMeanProportionInterval=${args.initialMeanProportionInterval}
153
+ .hideGaps=${args.hideGaps}
154
+ .pageSizes=${args.pageSizes}
155
+ .customColumns=${args.customColumns}
156
+ ></gs-queries-over-time>
157
+ </gs-app>
158
+ `,
159
+ };
160
+
161
+ export const Default: StoryObj<Required<QueriesOverTimeProps>> = {
162
+ ...Template,
163
+ };
164
+
165
+ export const WithFixedHeight: StoryObj<Required<QueriesOverTimeProps>> = {
166
+ ...Template,
167
+ args: {
168
+ ...Template.args,
169
+ height: '700px',
170
+ },
171
+ };
172
+
173
+ export const ByWeek: StoryObj<Required<QueriesOverTimeProps>> = {
174
+ ...Template,
175
+ args: {
176
+ ...Template.args,
177
+ lapisFilter: { pangoLineage: 'JN.1*', dateFrom: '2024-01-15', dateTo: '2024-02-11' },
178
+ granularity: 'week',
179
+ },
180
+ parameters: {
181
+ fetchMock: {
182
+ mocks: [
183
+ {
184
+ matcher: {
185
+ url: `${LAPIS_URL}/component/queriesOverTime`,
186
+ body: {
187
+ filters: {
188
+ pangoLineage: 'JN.1*',
189
+ dateFrom: '2024-01-15',
190
+ dateTo: '2024-02-11',
191
+ },
192
+ dateRanges: [
193
+ { dateFrom: '2024-01-15', dateTo: '2024-01-21' },
194
+ { dateFrom: '2024-01-22', dateTo: '2024-01-28' },
195
+ { dateFrom: '2024-01-29', dateTo: '2024-02-04' },
196
+ { dateFrom: '2024-02-05', dateTo: '2024-02-11' },
197
+ ],
198
+ },
199
+ matchPartialBody: true,
200
+ response: {
201
+ status: 200,
202
+ body: mockDefaultQueriesOverTime,
203
+ },
204
+ },
205
+ },
206
+ ],
207
+ },
208
+ },
209
+ };
210
+
211
+ export const WithCustomColumns: StoryObj<Required<QueriesOverTimeProps>> = {
212
+ ...Template,
213
+ args: {
214
+ ...Template.args,
215
+ customColumns: [
216
+ {
217
+ header: 'Jaccard Index',
218
+ values: {
219
+ 'S:F456L (single mutation)': 0.75,
220
+ 'R346T + F456L (combination)': 0.92,
221
+ 'C22916T or T22917G (nucleotide OR)': 0.58,
222
+ },
223
+ },
224
+ ],
225
+ },
226
+ };
227
+
228
+ export const HideGaps: StoryObj<Required<QueriesOverTimeProps>> = {
229
+ ...Template,
230
+ args: {
231
+ ...Template.args,
232
+ lapisFilter: { pangoLineage: 'JN.1*', dateFrom: '2024-01-15', dateTo: '2024-07-10' },
233
+ queries: [
234
+ {
235
+ displayLabel: 'S:F456L',
236
+ countQuery: 'S:456L',
237
+ coverageQuery: '!S:456N',
238
+ },
239
+ {
240
+ displayLabel: 'S:R346T',
241
+ countQuery: 'S:346T',
242
+ coverageQuery: '!S:346N',
243
+ },
244
+ {
245
+ displayLabel: 'S:Q493E',
246
+ countQuery: 'S:493E',
247
+ coverageQuery: '!S:493N',
248
+ },
249
+ ],
250
+ hideGaps: true,
251
+ },
252
+ parameters: {
253
+ fetchMock: {
254
+ mocks: [
255
+ {
256
+ matcher: {
257
+ url: `${LAPIS_URL}/component/queriesOverTime`,
258
+ body: {
259
+ filters: { pangoLineage: 'JN.1*', dateFrom: '2024-01-15', dateTo: '2024-07-10' },
260
+ dateRanges: [
261
+ { dateFrom: '2024-01-01', dateTo: '2024-01-31' },
262
+ { dateFrom: '2024-02-01', dateTo: '2024-02-29' },
263
+ { dateFrom: '2024-03-01', dateTo: '2024-03-31' },
264
+ { dateFrom: '2024-04-01', dateTo: '2024-04-30' },
265
+ { dateFrom: '2024-05-01', dateTo: '2024-05-31' },
266
+ { dateFrom: '2024-06-01', dateTo: '2024-06-30' },
267
+ { dateFrom: '2024-07-01', dateTo: '2024-07-31' },
268
+ ],
269
+ },
270
+ matchPartialBody: true,
271
+ response: {
272
+ status: 200,
273
+ body: mockWithGapsQueriesOverTime,
274
+ },
275
+ },
276
+ },
277
+ ],
278
+ },
279
+ },
280
+ };
281
+
282
+ export const SmallWidth: StoryObj<Required<QueriesOverTimeProps>> = {
283
+ ...Template,
284
+ args: {
285
+ ...Template.args,
286
+ width: '300px',
287
+ },
288
+ };