@genspectrum/dashboard-components 0.10.2 → 0.10.3
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/README.md +19 -19
- package/custom-elements.json +49 -33
- package/dist/assets/{mutationOverTimeWorker-Di6yP1e6.js.map → mutationOverTimeWorker-CNg_ztNp.js.map} +1 -1
- package/dist/components.d.ts +49 -60
- package/dist/components.js +203 -45
- package/dist/components.js.map +1 -1
- package/dist/{dateRangeOption-du8H7LWu.js → dateRangeOption-DjtcAEWq.js} +16 -3
- package/dist/dateRangeOption-DjtcAEWq.js.map +1 -0
- package/dist/style.css +11 -5
- package/dist/util.d.ts +91 -42
- package/dist/util.js +1 -1
- package/package.json +2 -2
- package/src/preact/aggregatedData/aggregate.stories.tsx +14 -0
- package/src/preact/aggregatedData/aggregate.tsx +17 -15
- package/src/preact/components/error-boundary.stories.tsx +24 -3
- package/src/preact/components/error-boundary.tsx +3 -4
- package/src/preact/components/error-display.tsx +38 -17
- package/src/preact/components/tabs.tsx +2 -2
- package/src/preact/mutationFilter/mutation-filter.tsx +26 -13
- package/src/preact/mutations/mutations.tsx +16 -12
- package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +14 -0
- package/src/preact/mutationsOverTime/mutations-over-time.tsx +18 -14
- package/src/preact/numberSequencesOverTime/number-sequences-over-time.stories.tsx +14 -0
- package/src/preact/numberSequencesOverTime/number-sequences-over-time.tsx +22 -14
- package/src/preact/prevalenceOverTime/prevalence-over-time.stories.tsx +14 -0
- package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +28 -19
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.stories.tsx +14 -0
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +18 -15
- package/src/preact/shared/charts/confideceInterval.ts +10 -8
- package/src/preact/shared/charts/getYAxisMax.ts +10 -5
- package/src/preact/statistic/statistics.tsx +10 -8
- package/src/preact/textInput/text-input.stories.tsx +14 -0
- package/src/preact/textInput/text-input.tsx +16 -11
- package/src/query/queryAggregateData.ts +2 -1
- package/src/types.ts +12 -1
- package/src/utilEntrypoint.ts +7 -0
- package/src/web-components/app.stories.ts +17 -2
- package/src/web-components/app.ts +17 -5
- package/src/web-components/input/gs-mutation-filter.stories.ts +2 -0
- package/src/web-components/input/gs-text-input.tsx +2 -2
- package/src/web-components/introduction.mdx +4 -4
- package/src/web-components/visualization/data_visualization_statistical_analysis.mdx +3 -3
- package/src/web-components/visualization/gs-mutations-over-time.tsx +1 -3
- package/src/web-components/visualization/gs-mutations.tsx +1 -3
- package/src/web-components/visualization/gs-number-sequences-over-time.tsx +1 -3
- package/src/web-components/visualization/gs-prevalence-over-time.tsx +3 -6
- package/src/web-components/visualization/gs-relative-growth-advantage.tsx +1 -5
- package/standalone-bundle/assets/mutationOverTimeWorker-cIyshfj_.js.map +1 -1
- package/standalone-bundle/dashboard-components.js +6079 -5944
- package/standalone-bundle/dashboard-components.js.map +1 -1
- package/standalone-bundle/style.css +1 -1
- package/dist/dateRangeOption-du8H7LWu.js.map +0 -1
|
@@ -77,7 +77,7 @@ export const ErrorDisplay: FunctionComponent<ErrorDisplayProps> = ({ error, rese
|
|
|
77
77
|
</button>
|
|
78
78
|
</form>
|
|
79
79
|
<h1 class='text-lg'>{details.headline}</h1>
|
|
80
|
-
<
|
|
80
|
+
<div class='py-4'>{details.message}</div>
|
|
81
81
|
</div>
|
|
82
82
|
<form method='dialog' class='modal-backdrop'>
|
|
83
83
|
<button>close</button>
|
|
@@ -129,26 +129,47 @@ function getDisplayedErrorMessage(error: Error) {
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
if (error instanceof InvalidPropsError) {
|
|
132
|
-
const firstError = error.zodError.errors[0];
|
|
133
|
-
let message = error.zodError.issues
|
|
134
|
-
.map((issue) => {
|
|
135
|
-
const actual =
|
|
136
|
-
issue.path[0] in error.componentProps
|
|
137
|
-
? ` '${JSON.stringify(error.componentProps[issue.path[0]])}'`
|
|
138
|
-
: '';
|
|
139
|
-
return `Unexpected value${actual} for "${issue.path.join('.')}": ${issue.message}`;
|
|
140
|
-
})
|
|
141
|
-
.join(' - ');
|
|
142
|
-
|
|
143
|
-
if (firstError.code === 'invalid_type' && firstError.received === 'null') {
|
|
144
|
-
message = `Is the "${firstError.path[0]}" attribute in the HTML of the correct type? ${message}`;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
132
|
return {
|
|
148
133
|
headline: 'Error - Invalid component attributes',
|
|
149
|
-
details: { headline: 'Invalid component attributes', message },
|
|
134
|
+
details: { headline: 'Invalid component attributes', message: <ZodErrorDetails error={error} /> },
|
|
150
135
|
};
|
|
151
136
|
}
|
|
152
137
|
|
|
153
138
|
return { headline: 'Error', details: undefined };
|
|
154
139
|
}
|
|
140
|
+
|
|
141
|
+
function ZodErrorDetails({ error }: { error: InvalidPropsError }) {
|
|
142
|
+
const firstError = error.zodError.errors[0];
|
|
143
|
+
return (
|
|
144
|
+
<>
|
|
145
|
+
<p>
|
|
146
|
+
<span className='font-bold'>You are a regular user?</span> Unfortunately, there is nothing you can do at
|
|
147
|
+
the moment. This component is misconfigured. Please contact the administrator of this page.
|
|
148
|
+
</p>
|
|
149
|
+
<p>
|
|
150
|
+
<span className='font-bold'>You are the administrator of this page?</span> You supplied invalid
|
|
151
|
+
attributes to this component. Please check the browser console for more detailed error messages.
|
|
152
|
+
</p>
|
|
153
|
+
{firstError.code === 'invalid_type' && firstError.received === 'null' && (
|
|
154
|
+
<p>
|
|
155
|
+
Is the "{firstError.path[0]}" attribute in the HTML of the correct type? The attribute is expected
|
|
156
|
+
to be of type "{firstError.expected}".
|
|
157
|
+
</p>
|
|
158
|
+
)}
|
|
159
|
+
<p>This is a summary of the unexpected attribute values:</p>
|
|
160
|
+
<ul class='m-4 list-outside list-disc '>
|
|
161
|
+
{error.zodError.issues.map((issue, index) => {
|
|
162
|
+
const actual =
|
|
163
|
+
issue.path[0] in error.componentProps
|
|
164
|
+
? `'${JSON.stringify(error.componentProps[issue.path[0]])}'`
|
|
165
|
+
: '';
|
|
166
|
+
return (
|
|
167
|
+
<li key={index}>
|
|
168
|
+
Unexpected value {actual} for "{issue.path.join('.')}": {issue.message}
|
|
169
|
+
</li>
|
|
170
|
+
);
|
|
171
|
+
})}
|
|
172
|
+
</ul>
|
|
173
|
+
</>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
@@ -13,7 +13,7 @@ interface ComponentTabsProps {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
const Tabs: FunctionComponent<ComponentTabsProps> = ({ tabs, toolbar }) => {
|
|
16
|
-
const [activeTab, setActiveTab] = useState(tabs[0]
|
|
16
|
+
const [activeTab, setActiveTab] = useState(tabs[0]?.title);
|
|
17
17
|
const [heightOfTabs, setHeightOfTabs] = useState('3rem');
|
|
18
18
|
const tabRef = useRef<HTMLDivElement>(null);
|
|
19
19
|
|
|
@@ -65,7 +65,7 @@ const Tabs: FunctionComponent<ComponentTabsProps> = ({ tabs, toolbar }) => {
|
|
|
65
65
|
{toolbar && <div className='py-2 flex flex-wrap gap-y-1'>{toolbarElement}</div>}
|
|
66
66
|
</div>
|
|
67
67
|
<div
|
|
68
|
-
className={`p-2 border-2 border-gray-100 rounded-b-md rounded-tr-md ${activeTab === tabs[0]
|
|
68
|
+
className={`p-2 border-2 border-gray-100 rounded-b-md rounded-tr-md ${activeTab === tabs[0]?.title ? '' : 'rounded-tl-md'}`}
|
|
69
69
|
style={{ height: `calc(100% - ${heightOfTabs})` }}
|
|
70
70
|
>
|
|
71
71
|
{tabs.map((tab) => (
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type FunctionComponent } from 'preact';
|
|
2
|
-
import { useContext,
|
|
2
|
+
import { useContext, useRef, useState } from 'preact/hooks';
|
|
3
|
+
import z from 'zod';
|
|
3
4
|
|
|
4
5
|
import { MutationFilterInfo } from './mutation-filter-info';
|
|
5
6
|
import { parseAndValidateMutation, type ParsedMutationFilter } from './parseAndValidateMutation';
|
|
@@ -9,13 +10,23 @@ import { ReferenceGenomeContext } from '../ReferenceGenomeContext';
|
|
|
9
10
|
import { ErrorBoundary } from '../components/error-boundary';
|
|
10
11
|
import { singleGraphColorRGBByName } from '../shared/charts/colors';
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
const selectedMutationFilterStringsSchema = z.object({
|
|
14
|
+
nucleotideMutations: z.array(z.string()),
|
|
15
|
+
aminoAcidMutations: z.array(z.string()),
|
|
16
|
+
nucleotideInsertions: z.array(z.string()),
|
|
17
|
+
aminoAcidInsertions: z.array(z.string()),
|
|
18
|
+
});
|
|
19
|
+
export type SelectedMutationFilterStrings = z.infer<typeof selectedMutationFilterStringsSchema>;
|
|
20
|
+
const mutationFilterInnerPropsSchema = z.object({
|
|
21
|
+
initialValue: z.union([selectedMutationFilterStringsSchema.optional(), z.array(z.string()), z.undefined()]),
|
|
22
|
+
});
|
|
15
23
|
|
|
16
|
-
|
|
17
|
-
width: string
|
|
18
|
-
}
|
|
24
|
+
const mutationFilterPropsSchema = mutationFilterInnerPropsSchema.extend({
|
|
25
|
+
width: z.string(),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export type MutationFilterInnerProps = z.infer<typeof mutationFilterInnerPropsSchema>;
|
|
29
|
+
export type MutationFilterProps = z.infer<typeof mutationFilterPropsSchema>;
|
|
19
30
|
|
|
20
31
|
export type SelectedFilters = {
|
|
21
32
|
nucleotideMutations: (SubstitutionClass | DeletionClass)[];
|
|
@@ -24,13 +35,15 @@ export type SelectedFilters = {
|
|
|
24
35
|
aminoAcidInsertions: InsertionClass[];
|
|
25
36
|
};
|
|
26
37
|
|
|
27
|
-
export
|
|
28
|
-
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export const MutationFilter: FunctionComponent<MutationFilterProps> = ({ initialValue, width }) => {
|
|
38
|
+
export const MutationFilter: FunctionComponent<MutationFilterProps> = (props) => {
|
|
39
|
+
const { width, initialValue } = props;
|
|
32
40
|
return (
|
|
33
|
-
<ErrorBoundary
|
|
41
|
+
<ErrorBoundary
|
|
42
|
+
size={{ height: '3.375rem', width }}
|
|
43
|
+
layout='horizontal'
|
|
44
|
+
schema={mutationFilterPropsSchema}
|
|
45
|
+
componentProps={props}
|
|
46
|
+
>
|
|
34
47
|
<div style={width}>
|
|
35
48
|
<MutationFilterInner initialValue={initialValue} />
|
|
36
49
|
</div>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type FunctionComponent } from 'preact';
|
|
2
2
|
import { type Dispatch, type StateUpdater, useContext, useState } from 'preact/hooks';
|
|
3
|
+
import z from 'zod';
|
|
3
4
|
|
|
4
5
|
import { getInsertionsTableData } from './getInsertionsTableData';
|
|
5
6
|
import { getMutationsTableData } from './getMutationsTableData';
|
|
@@ -9,9 +10,10 @@ import MutationsTable from './mutations-table';
|
|
|
9
10
|
import { filterMutationsData, queryMutationsData } from './queryMutations';
|
|
10
11
|
import {
|
|
11
12
|
type InsertionEntry,
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
lapisFilterSchema,
|
|
14
|
+
sequenceTypeSchema,
|
|
14
15
|
type SubstitutionOrDeletionEntry,
|
|
16
|
+
views,
|
|
15
17
|
} from '../../types';
|
|
16
18
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
17
19
|
import { CsvDownloadButton } from '../components/csv-download-button';
|
|
@@ -28,23 +30,25 @@ import { type DisplayedSegment, SegmentSelector, useDisplayedSegments } from '..
|
|
|
28
30
|
import Tabs from '../components/tabs';
|
|
29
31
|
import { useQuery } from '../useQuery';
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
const viewSchema = z.union([z.literal(views.table), z.literal(views.grid), z.literal(views.insertions)]);
|
|
34
|
+
export type View = z.infer<typeof viewSchema>;
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
36
|
+
const mutationsPropsSchema = z.object({
|
|
37
|
+
lapisFilter: lapisFilterSchema,
|
|
38
|
+
sequenceType: sequenceTypeSchema,
|
|
39
|
+
views: viewSchema.array(),
|
|
40
|
+
pageSize: z.union([z.boolean(), z.number()]),
|
|
41
|
+
width: z.string(),
|
|
42
|
+
height: z.string(),
|
|
43
|
+
});
|
|
44
|
+
export type MutationsProps = z.infer<typeof mutationsPropsSchema>;
|
|
41
45
|
|
|
42
46
|
export const Mutations: FunctionComponent<MutationsProps> = (componentProps) => {
|
|
43
47
|
const { width, height } = componentProps;
|
|
44
48
|
const size = { height, width };
|
|
45
49
|
|
|
46
50
|
return (
|
|
47
|
-
<ErrorBoundary size={size}>
|
|
51
|
+
<ErrorBoundary size={size} componentProps={componentProps} schema={mutationsPropsSchema}>
|
|
48
52
|
<ResizeContainer size={size}>
|
|
49
53
|
<MutationsInner {...componentProps} />
|
|
50
54
|
</ResizeContainer>
|
|
@@ -6,6 +6,7 @@ import { LAPIS_URL } from '../../constants';
|
|
|
6
6
|
import referenceGenome from '../../lapisApi/__mockData__/referenceGenome.json';
|
|
7
7
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
8
8
|
import { ReferenceGenomeContext } from '../ReferenceGenomeContext';
|
|
9
|
+
import { expectInvalidAttributesErrorMessage } from '../shared/stories/expectInvalidAttributesErrorMessage';
|
|
9
10
|
|
|
10
11
|
const meta: Meta<MutationsOverTimeProps> = {
|
|
11
12
|
title: 'Visualization/Mutation over time',
|
|
@@ -155,3 +156,16 @@ export const ShowsNoDataMessageForStrictFilters: StoryObj<MutationsOverTimeProps
|
|
|
155
156
|
);
|
|
156
157
|
},
|
|
157
158
|
};
|
|
159
|
+
|
|
160
|
+
export const WithNoLapisDateFieldField: StoryObj<MutationsOverTimeProps> = {
|
|
161
|
+
...Default,
|
|
162
|
+
args: {
|
|
163
|
+
...Default.args,
|
|
164
|
+
lapisDateField: '',
|
|
165
|
+
},
|
|
166
|
+
play: async ({ canvasElement, step }) => {
|
|
167
|
+
step('expect error message', async () => {
|
|
168
|
+
await expectInvalidAttributesErrorMessage(canvasElement, 'String must contain at least 1 character(s)');
|
|
169
|
+
});
|
|
170
|
+
},
|
|
171
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type FunctionComponent } from 'preact';
|
|
2
2
|
import { type Dispatch, type StateUpdater, useContext, useMemo, useState } from 'preact/hooks';
|
|
3
|
+
import z from 'zod';
|
|
3
4
|
|
|
4
5
|
// @ts-expect-error -- uses subpath imports and vite worker import
|
|
5
6
|
import MutationOverTimeWorker from '#mutationOverTime?worker&inline';
|
|
@@ -9,10 +10,11 @@ import { type MutationOverTimeWorkerResponse } from './mutationOverTimeWorker';
|
|
|
9
10
|
import MutationsOverTimeGrid from './mutations-over-time-grid';
|
|
10
11
|
import { type MutationOverTimeQuery } from '../../query/queryMutationsOverTime';
|
|
11
12
|
import {
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
lapisFilterSchema,
|
|
14
|
+
sequenceTypeSchema,
|
|
14
15
|
type SubstitutionOrDeletionEntry,
|
|
15
|
-
|
|
16
|
+
temporalGranularitySchema,
|
|
17
|
+
views,
|
|
16
18
|
} from '../../types';
|
|
17
19
|
import { type Deletion, type Substitution } from '../../utils/mutations';
|
|
18
20
|
import { toTemporalClass } from '../../utils/temporalClass';
|
|
@@ -33,24 +35,26 @@ import { type DisplayedSegment, SegmentSelector, useDisplayedSegments } from '..
|
|
|
33
35
|
import Tabs from '../components/tabs';
|
|
34
36
|
import { useWebWorker } from '../webWorkers/useWebWorker';
|
|
35
37
|
|
|
36
|
-
|
|
38
|
+
const viewSchema = z.literal(views.grid);
|
|
39
|
+
export type View = z.infer<typeof viewSchema>;
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
41
|
+
const mutationOverTimeSchema = z.object({
|
|
42
|
+
lapisFilter: lapisFilterSchema,
|
|
43
|
+
sequenceType: sequenceTypeSchema,
|
|
44
|
+
views: z.array(viewSchema),
|
|
45
|
+
granularity: temporalGranularitySchema,
|
|
46
|
+
lapisDateField: z.string().min(1),
|
|
47
|
+
width: z.string(),
|
|
48
|
+
height: z.string(),
|
|
49
|
+
});
|
|
50
|
+
export type MutationsOverTimeProps = z.infer<typeof mutationOverTimeSchema>;
|
|
47
51
|
|
|
48
52
|
export const MutationsOverTime: FunctionComponent<MutationsOverTimeProps> = (componentProps) => {
|
|
49
53
|
const { width, height } = componentProps;
|
|
50
54
|
const size = { height, width };
|
|
51
55
|
|
|
52
56
|
return (
|
|
53
|
-
<ErrorBoundary size={size}>
|
|
57
|
+
<ErrorBoundary size={size} schema={mutationOverTimeSchema} componentProps={componentProps}>
|
|
54
58
|
<ResizeContainer size={size}>
|
|
55
59
|
<MutationsOverTimeInner {...componentProps} />
|
|
56
60
|
</ResizeContainer>
|
|
@@ -6,6 +6,7 @@ import oneVariantEG from '../../preact/numberSequencesOverTime/__mockData__/oneV
|
|
|
6
6
|
import twoVariantsEG from '../../preact/numberSequencesOverTime/__mockData__/twoVariantsEG.json';
|
|
7
7
|
import twoVariantsJN1 from '../../preact/numberSequencesOverTime/__mockData__/twoVariantsJN1.json';
|
|
8
8
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
9
|
+
import { expectInvalidAttributesErrorMessage } from '../shared/stories/expectInvalidAttributesErrorMessage';
|
|
9
10
|
|
|
10
11
|
export default {
|
|
11
12
|
title: 'Visualization/NumberSequencesOverTime',
|
|
@@ -129,3 +130,16 @@ export const TwoVariants = {
|
|
|
129
130
|
},
|
|
130
131
|
},
|
|
131
132
|
};
|
|
133
|
+
|
|
134
|
+
export const WithNoLapisDateField: StoryObj<NumberSequencesOverTimeProps> = {
|
|
135
|
+
...Template,
|
|
136
|
+
args: {
|
|
137
|
+
...Template.args,
|
|
138
|
+
lapisDateField: '',
|
|
139
|
+
},
|
|
140
|
+
play: async ({ canvasElement, step }) => {
|
|
141
|
+
step('expect error message', async () => {
|
|
142
|
+
await expectInvalidAttributesErrorMessage(canvasElement, 'String must contain at least 1 character(s)');
|
|
143
|
+
});
|
|
144
|
+
},
|
|
145
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type FunctionComponent } from 'preact';
|
|
2
2
|
import { useContext, useState } from 'preact/hooks';
|
|
3
|
+
import z from 'zod';
|
|
3
4
|
|
|
4
5
|
import { getNumberOfSequencesOverTimeTableData } from './getNumberOfSequencesOverTimeTableData';
|
|
5
6
|
import { NumberSequencesOverTimeBarChart } from './number-sequences-over-time-bar-chart';
|
|
@@ -9,7 +10,7 @@ import {
|
|
|
9
10
|
type NumberOfSequencesDatasets,
|
|
10
11
|
queryNumberOfSequencesOverTime,
|
|
11
12
|
} from '../../query/queryNumberOfSequencesOverTime';
|
|
12
|
-
import
|
|
13
|
+
import { namedLapisFilterSchema, temporalGranularitySchema, views } from '../../types';
|
|
13
14
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
14
15
|
import { CsvDownloadButton } from '../components/csv-download-button';
|
|
15
16
|
import { ErrorBoundary } from '../components/error-boundary';
|
|
@@ -23,25 +24,32 @@ import Tabs from '../components/tabs';
|
|
|
23
24
|
import type { ScaleType } from '../shared/charts/getYAxisScale';
|
|
24
25
|
import { useQuery } from '../useQuery';
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
27
|
+
const numberSequencesOverTimeViewSchema = z.union([
|
|
28
|
+
z.literal(views.bar),
|
|
29
|
+
z.literal(views.line),
|
|
30
|
+
z.literal(views.table),
|
|
31
|
+
]);
|
|
32
|
+
export type NumberSequencesOverTimeView = z.infer<typeof numberSequencesOverTimeViewSchema>;
|
|
33
|
+
|
|
34
|
+
const numberSequencesOverTimePropsSchema = z.object({
|
|
35
|
+
width: z.string(),
|
|
36
|
+
height: z.string(),
|
|
37
|
+
lapisFilter: z.union([namedLapisFilterSchema, z.array(namedLapisFilterSchema)]),
|
|
38
|
+
lapisDateField: z.string().min(1),
|
|
39
|
+
views: z.array(numberSequencesOverTimeViewSchema),
|
|
40
|
+
granularity: temporalGranularitySchema,
|
|
41
|
+
smoothingWindow: z.number(),
|
|
42
|
+
pageSize: z.union([z.boolean(), z.number()]),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export type NumberSequencesOverTimeProps = z.infer<typeof numberSequencesOverTimePropsSchema>;
|
|
38
46
|
|
|
39
47
|
export const NumberSequencesOverTime = (componentProps: NumberSequencesOverTimeProps) => {
|
|
40
48
|
const { width, height } = componentProps;
|
|
41
49
|
const size = { height, width };
|
|
42
50
|
|
|
43
51
|
return (
|
|
44
|
-
<ErrorBoundary size={size}>
|
|
52
|
+
<ErrorBoundary size={size} componentProps={componentProps} schema={numberSequencesOverTimePropsSchema}>
|
|
45
53
|
<ResizeContainer size={size}>
|
|
46
54
|
<NumberSequencesOverTimeInner {...componentProps} />
|
|
47
55
|
</ResizeContainer>
|
|
@@ -10,6 +10,7 @@ import numeratorFilterNoData from './__mockData__/numeratorFilterNoData.json';
|
|
|
10
10
|
import numeratorOneDataset from './__mockData__/numeratorFilterOneDataset.json';
|
|
11
11
|
import { PrevalenceOverTime, type PrevalenceOverTimeProps } from './prevalence-over-time';
|
|
12
12
|
import { AGGREGATED_ENDPOINT, LAPIS_URL } from '../../constants';
|
|
13
|
+
import { expectInvalidAttributesErrorMessage } from '../shared/stories/expectInvalidAttributesErrorMessage';
|
|
13
14
|
|
|
14
15
|
export default {
|
|
15
16
|
title: 'Visualization/PrevalenceOverTime',
|
|
@@ -256,3 +257,16 @@ export const ShowsNoDataBanner: StoryObj<PrevalenceOverTimeProps> = {
|
|
|
256
257
|
});
|
|
257
258
|
},
|
|
258
259
|
};
|
|
260
|
+
|
|
261
|
+
export const WithNoLapisDateField: StoryObj<PrevalenceOverTimeProps> = {
|
|
262
|
+
...OneVariant,
|
|
263
|
+
args: {
|
|
264
|
+
...OneVariant.args,
|
|
265
|
+
lapisDateField: '',
|
|
266
|
+
},
|
|
267
|
+
play: async ({ canvasElement, step }) => {
|
|
268
|
+
step('expect error message', async () => {
|
|
269
|
+
await expectInvalidAttributesErrorMessage(canvasElement, 'String must contain at least 1 character(s)');
|
|
270
|
+
});
|
|
271
|
+
},
|
|
272
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type FunctionComponent } from 'preact';
|
|
2
2
|
import { useContext, useEffect, useState } from 'preact/hooks';
|
|
3
|
+
import z from 'zod';
|
|
3
4
|
|
|
4
5
|
import { getPrevalenceOverTimeTableData } from './getPrevalenceOverTimeTableData';
|
|
5
6
|
import PrevalenceOverTimeBarChart from './prevalence-over-time-bar-chart';
|
|
@@ -7,7 +8,7 @@ import PrevalenceOverTimeBubbleChart from './prevalence-over-time-bubble-chart';
|
|
|
7
8
|
import PrevalenceOverTimeLineChart from './prevalence-over-time-line-chart';
|
|
8
9
|
import PrevalenceOverTimeTable from './prevalence-over-time-table';
|
|
9
10
|
import { type PrevalenceOverTimeData, queryPrevalenceOverTime } from '../../query/queryPrevalenceOverTime';
|
|
10
|
-
import {
|
|
11
|
+
import { lapisFilterSchema, namedLapisFilterSchema, temporalGranularitySchema, views } from '../../types';
|
|
11
12
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
12
13
|
import { ConfidenceIntervalSelector } from '../components/confidence-interval-selector';
|
|
13
14
|
import { CsvDownloadButton } from '../components/csv-download-button';
|
|
@@ -19,34 +20,42 @@ import { NoDataDisplay } from '../components/no-data-display';
|
|
|
19
20
|
import { ResizeContainer } from '../components/resize-container';
|
|
20
21
|
import { ScalingSelector } from '../components/scaling-selector';
|
|
21
22
|
import Tabs from '../components/tabs';
|
|
22
|
-
import { type ConfidenceIntervalMethod } from '../shared/charts/confideceInterval';
|
|
23
|
-
import {
|
|
23
|
+
import { type ConfidenceIntervalMethod, confidenceIntervalMethodSchema } from '../shared/charts/confideceInterval';
|
|
24
|
+
import { axisMaxSchema } from '../shared/charts/getYAxisMax';
|
|
24
25
|
import { type ScaleType } from '../shared/charts/getYAxisScale';
|
|
25
26
|
import { useQuery } from '../useQuery';
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
const viewSchema = z.union([
|
|
29
|
+
z.literal(views.table),
|
|
30
|
+
z.literal(views.bar),
|
|
31
|
+
z.literal(views.line),
|
|
32
|
+
z.literal(views.bubble),
|
|
33
|
+
]);
|
|
34
|
+
export type View = z.infer<typeof viewSchema>;
|
|
28
35
|
|
|
29
|
-
|
|
30
|
-
width: string
|
|
31
|
-
height: string
|
|
32
|
-
numeratorFilter:
|
|
33
|
-
denominatorFilter:
|
|
34
|
-
granularity:
|
|
35
|
-
smoothingWindow: number
|
|
36
|
-
views:
|
|
37
|
-
confidenceIntervalMethods:
|
|
38
|
-
lapisDateField: string
|
|
39
|
-
pageSize: boolean
|
|
40
|
-
yAxisMaxLinear:
|
|
41
|
-
yAxisMaxLogarithmic:
|
|
42
|
-
}
|
|
36
|
+
const prevalenceOverTimePropsSchema = z.object({
|
|
37
|
+
width: z.string(),
|
|
38
|
+
height: z.string(),
|
|
39
|
+
numeratorFilter: z.union([namedLapisFilterSchema, z.array(namedLapisFilterSchema)]),
|
|
40
|
+
denominatorFilter: lapisFilterSchema,
|
|
41
|
+
granularity: temporalGranularitySchema,
|
|
42
|
+
smoothingWindow: z.number(),
|
|
43
|
+
views: z.array(viewSchema),
|
|
44
|
+
confidenceIntervalMethods: z.array(confidenceIntervalMethodSchema),
|
|
45
|
+
lapisDateField: z.string().min(1),
|
|
46
|
+
pageSize: z.union([z.boolean(), z.number()]),
|
|
47
|
+
yAxisMaxLinear: axisMaxSchema,
|
|
48
|
+
yAxisMaxLogarithmic: axisMaxSchema,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export type PrevalenceOverTimeProps = z.infer<typeof prevalenceOverTimePropsSchema>;
|
|
43
52
|
|
|
44
53
|
export const PrevalenceOverTime: FunctionComponent<PrevalenceOverTimeProps> = (componentProps) => {
|
|
45
54
|
const { width, height } = componentProps;
|
|
46
55
|
const size = { height, width };
|
|
47
56
|
|
|
48
57
|
return (
|
|
49
|
-
<ErrorBoundary size={size}>
|
|
58
|
+
<ErrorBoundary size={size} schema={prevalenceOverTimePropsSchema} componentProps={componentProps}>
|
|
50
59
|
<ResizeContainer size={size}>
|
|
51
60
|
<PrevalenceOverTimeInner {...componentProps} />
|
|
52
61
|
</ResizeContainer>
|
|
@@ -6,6 +6,7 @@ import numerator from './__mockData__/numeratorFilter.json';
|
|
|
6
6
|
import { RelativeGrowthAdvantage, type RelativeGrowthAdvantageProps } from './relative-growth-advantage';
|
|
7
7
|
import { AGGREGATED_ENDPOINT, LAPIS_URL } from '../../constants';
|
|
8
8
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
9
|
+
import { expectInvalidAttributesErrorMessage } from '../shared/stories/expectInvalidAttributesErrorMessage';
|
|
9
10
|
|
|
10
11
|
export default {
|
|
11
12
|
title: 'Visualization/RelativeGrowthAdvantage',
|
|
@@ -190,3 +191,16 @@ export const TooFewDataToComputeGrowthAdvantage: StoryObj<RelativeGrowthAdvantag
|
|
|
190
191
|
});
|
|
191
192
|
},
|
|
192
193
|
};
|
|
194
|
+
|
|
195
|
+
export const WithNoLapisDateField: StoryObj<RelativeGrowthAdvantageProps> = {
|
|
196
|
+
...Primary,
|
|
197
|
+
args: {
|
|
198
|
+
...Primary.args,
|
|
199
|
+
lapisDateField: '',
|
|
200
|
+
},
|
|
201
|
+
play: async ({ canvasElement, step }) => {
|
|
202
|
+
step('expect error message', async () => {
|
|
203
|
+
await expectInvalidAttributesErrorMessage(canvasElement, 'String must contain at least 1 character(s)');
|
|
204
|
+
});
|
|
205
|
+
},
|
|
206
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type FunctionComponent } from 'preact';
|
|
2
2
|
import { useContext, useState } from 'preact/hooks';
|
|
3
|
+
import z from 'zod';
|
|
3
4
|
|
|
4
5
|
import RelativeGrowthAdvantageChart from './relative-growth-advantage-chart';
|
|
5
6
|
import {
|
|
@@ -7,7 +8,7 @@ import {
|
|
|
7
8
|
queryRelativeGrowthAdvantage,
|
|
8
9
|
type RelativeGrowthAdvantageData,
|
|
9
10
|
} from '../../query/queryRelativeGrowthAdvantage';
|
|
10
|
-
import {
|
|
11
|
+
import { lapisFilterSchema, views } from '../../types';
|
|
11
12
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
12
13
|
import { ErrorBoundary } from '../components/error-boundary';
|
|
13
14
|
import { Fullscreen } from '../components/fullscreen';
|
|
@@ -17,29 +18,31 @@ import { NoDataDisplay } from '../components/no-data-display';
|
|
|
17
18
|
import { ResizeContainer } from '../components/resize-container';
|
|
18
19
|
import { ScalingSelector } from '../components/scaling-selector';
|
|
19
20
|
import Tabs from '../components/tabs';
|
|
20
|
-
import {
|
|
21
|
+
import { yAxisMaxConfigSchema } from '../shared/charts/getYAxisMax';
|
|
21
22
|
import { type ScaleType } from '../shared/charts/getYAxisScale';
|
|
22
23
|
import { useQuery } from '../useQuery';
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
25
|
+
const viewSchema = z.literal(views.line);
|
|
26
|
+
export type View = z.infer<typeof viewSchema>;
|
|
27
|
+
|
|
28
|
+
export const relativeGrowthAdvantagePropsSchema = z.object({
|
|
29
|
+
width: z.string(),
|
|
30
|
+
height: z.string(),
|
|
31
|
+
numeratorFilter: lapisFilterSchema,
|
|
32
|
+
denominatorFilter: lapisFilterSchema,
|
|
33
|
+
generationTime: z.number(),
|
|
34
|
+
views: z.array(viewSchema),
|
|
35
|
+
lapisDateField: z.string().min(1),
|
|
36
|
+
yAxisMaxConfig: yAxisMaxConfigSchema,
|
|
37
|
+
});
|
|
38
|
+
export type RelativeGrowthAdvantageProps = z.infer<typeof relativeGrowthAdvantagePropsSchema>;
|
|
36
39
|
|
|
37
40
|
export const RelativeGrowthAdvantage: FunctionComponent<RelativeGrowthAdvantageProps> = (componentProps) => {
|
|
38
41
|
const { width, height } = componentProps;
|
|
39
42
|
const size = { height, width };
|
|
40
43
|
|
|
41
44
|
return (
|
|
42
|
-
<ErrorBoundary size={size}>
|
|
45
|
+
<ErrorBoundary size={size} schema={relativeGrowthAdvantagePropsSchema} componentProps={componentProps}>
|
|
43
46
|
<ResizeContainer size={size}>
|
|
44
47
|
<RelativeGrowthAdvantageInner {...componentProps} />
|
|
45
48
|
</ResizeContainer>
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import z from 'zod';
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* calculateWilsonInterval calculates the Wilson score interval for 95% confidence.
|
|
5
|
+
*This function is based on https://github.com/erikfox/wilson-interval, but without high precision math.
|
|
6
|
+
* observed - number of observed positive outcomes
|
|
7
|
+
* sample - number of experiments or size of the sample
|
|
8
|
+
*/
|
|
8
9
|
export function wilson95PercentConfidenceInterval(observed: number, sample: number) {
|
|
9
10
|
const p = observed / sample;
|
|
10
11
|
const n = sample;
|
|
@@ -31,4 +32,5 @@ export const confidenceIntervalDataLabel = (
|
|
|
31
32
|
return `${label}${value.toFixed(3)} (${lowerLimit?.toFixed(3)} - ${upperLimit?.toFixed(3)})`;
|
|
32
33
|
};
|
|
33
34
|
|
|
34
|
-
export
|
|
35
|
+
export const confidenceIntervalMethodSchema = z.union([z.literal('wilson'), z.literal('none')]);
|
|
36
|
+
export type ConfidenceIntervalMethod = z.infer<typeof confidenceIntervalMethodSchema>;
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
linear?: AxisMax;
|
|
3
|
-
logarithmic?: AxisMax;
|
|
4
|
-
}
|
|
1
|
+
import z from 'zod';
|
|
5
2
|
|
|
6
|
-
export
|
|
3
|
+
export const axisMaxSchema = z.union([z.literal('maxInData'), z.literal('limitTo1'), z.number()]);
|
|
4
|
+
export type AxisMax = z.infer<typeof axisMaxSchema>;
|
|
5
|
+
|
|
6
|
+
export const yAxisMaxConfigSchema = z.object({
|
|
7
|
+
linear: axisMaxSchema.optional(),
|
|
8
|
+
logarithmic: axisMaxSchema.optional(),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export type YAxisMaxConfig = z.infer<typeof yAxisMaxConfigSchema>;
|
|
7
12
|
|
|
8
13
|
export const getYAxisMax = (maxInData: number, axisMax?: AxisMax) => {
|
|
9
14
|
if (!axisMax) {
|