@genspectrum/dashboard-components 0.1.5 → 0.3.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 +1161 -928
- package/dist/dashboard-components.js +663 -237
- package/dist/dashboard-components.js.map +1 -1
- package/dist/genspectrum-components.d.ts +177 -140
- package/dist/style.css +247 -50
- package/package.json +2 -3
- package/src/constants.ts +1 -1
- package/src/lapisApi/lapisApi.ts +46 -2
- package/src/lapisApi/lapisTypes.ts +14 -0
- package/src/preact/aggregatedData/aggregate.stories.tsx +4 -2
- package/src/preact/aggregatedData/aggregate.tsx +31 -29
- package/src/preact/components/error-boundary.stories.tsx +54 -0
- package/src/preact/components/error-boundary.tsx +22 -0
- package/src/preact/components/error-display.stories.tsx +32 -4
- package/src/preact/components/error-display.tsx +48 -1
- package/src/preact/components/loading-display.stories.tsx +6 -6
- package/src/preact/components/loading-display.tsx +1 -1
- package/src/preact/components/no-data-display.tsx +5 -1
- package/src/preact/components/resize-container.tsx +5 -14
- package/src/preact/dateRangeSelector/date-range-selector.stories.tsx +19 -0
- package/src/preact/dateRangeSelector/date-range-selector.tsx +38 -7
- package/src/preact/locationFilter/fetchAutocompletionList.ts +15 -1
- package/src/preact/locationFilter/location-filter.stories.tsx +23 -6
- package/src/preact/locationFilter/location-filter.tsx +28 -18
- package/src/preact/mutationComparison/mutation-comparison.stories.tsx +6 -3
- package/src/preact/mutationComparison/mutation-comparison.tsx +33 -32
- package/src/preact/mutationComparison/queryMutationData.ts +2 -3
- package/src/preact/mutationFilter/mutation-filter.stories.tsx +18 -3
- package/src/preact/mutationFilter/mutation-filter.tsx +26 -7
- package/src/preact/mutations/mutations.stories.tsx +6 -3
- package/src/preact/mutations/mutations.tsx +28 -26
- package/src/preact/prevalenceOverTime/prevalence-over-time.stories.tsx +14 -7
- package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +50 -32
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.stories.tsx +6 -3
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +46 -32
- package/src/preact/textInput/text-input.stories.tsx +26 -0
- package/src/preact/textInput/text-input.tsx +25 -3
- package/src/query/queryPrevalenceOverTime.ts +4 -10
- package/src/types.ts +4 -1
- package/src/web-components/ResizeContainer.mdx +13 -0
- package/src/web-components/app.stories.ts +1 -2
- package/src/web-components/app.ts +7 -3
- package/src/web-components/index.ts +1 -1
- package/src/web-components/input/{date-range-selector-component.stories.ts → gs-date-range-selector.stories.ts} +29 -4
- package/src/web-components/input/{date-range-selector-component.tsx → gs-date-range-selector.tsx} +32 -10
- package/src/web-components/input/{location-filter-component.stories.ts → gs-location-filter.stories.ts} +32 -5
- package/src/web-components/input/{location-filter-component.tsx → gs-location-filter.tsx} +11 -1
- package/src/web-components/input/{mutation-filter-component.stories.ts → gs-mutation-filter.stories.ts} +23 -4
- package/src/web-components/input/gs-mutation-filter.tsx +126 -0
- package/src/web-components/input/{text-input-component.stories.ts → gs-text-input.stories.ts} +34 -6
- package/src/web-components/input/{text-input-component.tsx → gs-text-input.tsx} +16 -4
- package/src/web-components/input/index.ts +4 -4
- package/src/web-components/input/introduction.mdx +11 -0
- package/src/web-components/introduction.mdx +15 -0
- package/src/web-components/visualization/data_visualization_statistical_analysis.mdx +26 -0
- package/src/web-components/{display/aggregate-component.stories.ts → visualization/gs-aggregate.stories.ts} +23 -11
- package/src/web-components/visualization/gs-aggregate.tsx +88 -0
- package/src/web-components/{display/mutation-comparison-component.stories.ts → visualization/gs-mutation-comparison.stories.ts} +21 -16
- package/src/web-components/{display/mutation-comparison-component.tsx → visualization/gs-mutation-comparison.tsx} +27 -18
- package/src/web-components/{display/mutations-component.stories.ts → visualization/gs-mutations.stories.ts} +20 -15
- package/src/web-components/{display/mutations-component.tsx → visualization/gs-mutations.tsx} +20 -10
- package/src/web-components/{display/prevalence-over-time-component.stories.ts → visualization/gs-prevalence-over-time.stories.ts} +29 -20
- package/src/web-components/{display/prevalence-over-time-component.tsx → visualization/gs-prevalence-over-time.tsx} +47 -22
- package/src/web-components/{display/relative-growth-advantage-component.stories.ts → visualization/gs-relative-growth-advantage.stories.ts} +12 -7
- package/src/web-components/{display/relative-growth-advantage-component.tsx → visualization/gs-relative-growth-advantage.tsx} +21 -9
- package/src/web-components/visualization/index.ts +5 -0
- package/src/web-components/display/aggregate-component.tsx +0 -72
- package/src/web-components/display/index.ts +0 -5
- package/src/web-components/input/mutation-filter-component.tsx +0 -83
|
@@ -8,9 +8,14 @@ import { LAPIS_URL } from '../../constants';
|
|
|
8
8
|
import '../app';
|
|
9
9
|
import { type MutationFilterProps } from '../../preact/mutationFilter/mutation-filter';
|
|
10
10
|
import { withinShadowRoot } from '../withinShadowRoot.story';
|
|
11
|
-
import './mutation-filter
|
|
11
|
+
import './gs-mutation-filter';
|
|
12
12
|
|
|
13
|
-
const codeExample = String.raw
|
|
13
|
+
const codeExample = String.raw`
|
|
14
|
+
<gs-mutation-filter
|
|
15
|
+
initialValue='["A123T"]'
|
|
16
|
+
width='100%'
|
|
17
|
+
height='6.5rem'
|
|
18
|
+
></gs-mutation-filter>`;
|
|
14
19
|
|
|
15
20
|
const meta: Meta<MutationFilterProps> = {
|
|
16
21
|
title: 'Input/Mutation filter',
|
|
@@ -21,12 +26,20 @@ const meta: Meta<MutationFilterProps> = {
|
|
|
21
26
|
},
|
|
22
27
|
fetchMock: {},
|
|
23
28
|
componentDocs: {
|
|
24
|
-
tag: 'gs-mutation-filter',
|
|
25
29
|
opensShadowDom: true,
|
|
26
30
|
expectsChildren: false,
|
|
27
31
|
codeExample,
|
|
28
32
|
},
|
|
29
33
|
}),
|
|
34
|
+
argTypes: {
|
|
35
|
+
initialValue: {
|
|
36
|
+
control: {
|
|
37
|
+
type: 'object',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
width: { control: 'text' },
|
|
41
|
+
height: { control: 'text' },
|
|
42
|
+
},
|
|
30
43
|
decorators: [withActions],
|
|
31
44
|
tags: ['autodocs'],
|
|
32
45
|
};
|
|
@@ -37,12 +50,18 @@ const Template: StoryObj<MutationFilterProps> = {
|
|
|
37
50
|
render: (args) => {
|
|
38
51
|
return html` <gs-app lapis="${LAPIS_URL}">
|
|
39
52
|
<div class="max-w-screen-lg">
|
|
40
|
-
<gs-mutation-filter
|
|
53
|
+
<gs-mutation-filter
|
|
54
|
+
.initialValue=${args.initialValue}
|
|
55
|
+
.width=${args.width}
|
|
56
|
+
.height=${args.height}
|
|
57
|
+
></gs-mutation-filter>
|
|
41
58
|
</div>
|
|
42
59
|
</gs-app>`;
|
|
43
60
|
},
|
|
44
61
|
args: {
|
|
45
62
|
initialValue: [],
|
|
63
|
+
width: '100%',
|
|
64
|
+
height: '3rem',
|
|
46
65
|
},
|
|
47
66
|
};
|
|
48
67
|
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { customElement, property } from 'lit/decorators.js';
|
|
2
|
+
|
|
3
|
+
import { ReferenceGenomesAwaiter } from '../../preact/components/ReferenceGenomesAwaiter';
|
|
4
|
+
import {
|
|
5
|
+
MutationFilter,
|
|
6
|
+
type MutationFilterProps,
|
|
7
|
+
type SelectedMutationFilterStrings,
|
|
8
|
+
} from '../../preact/mutationFilter/mutation-filter';
|
|
9
|
+
import type { Equals, Expect } from '../../utils/typeAssertions';
|
|
10
|
+
import { PreactLitAdapter } from '../PreactLitAdapter';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* ## Context
|
|
14
|
+
* This component provides an input field to specify filters for nucleotide and amino acid mutations and insertions.
|
|
15
|
+
*
|
|
16
|
+
* Input values have to be provided one at a time and submitted by pressing the Enter key or by clicking the '+' button.
|
|
17
|
+
* After submission, the component validates the input and fires an event with the selected mutations.
|
|
18
|
+
* All previously selected mutations are displayed at the input field and added to the event.
|
|
19
|
+
* Users can remove a mutation by clicking the 'x' button next to the mutation.
|
|
20
|
+
*
|
|
21
|
+
* ## Input Validation
|
|
22
|
+
*
|
|
23
|
+
* Validation of the input is performed according to the following rules:
|
|
24
|
+
*
|
|
25
|
+
* ### Mutations
|
|
26
|
+
*
|
|
27
|
+
* Mutations have to conform to the following format: `<gene/segment>:<symbol at reference><position><Substituted symbol/Deletion>`
|
|
28
|
+
* - Gene/segment: The gene or segment where the mutation occurs. Must be contained in the reference genome.
|
|
29
|
+
* (Optional for elements with only one gene/segment)
|
|
30
|
+
* - Symbol at reference: The symbol at the reference position. (Optional)
|
|
31
|
+
* - Position: The position of the mutation. (Required)
|
|
32
|
+
* - Substituted symbol/Deletion: The substituted symbol or '-' for a deletion. (Required)
|
|
33
|
+
*
|
|
34
|
+
* Examples: `S:614G`, `614G`, `614-`, `614G`
|
|
35
|
+
*
|
|
36
|
+
* ### Insertions
|
|
37
|
+
*
|
|
38
|
+
* Insertions have to conform to the following format: `ins_<gene/segment>:<position>:<Inserted symbols>`
|
|
39
|
+
* - Gene/segment: The gene or segment where the insertion occurs. Must be contained in the reference genome.
|
|
40
|
+
* (Optional for elements with only one gene/segment)
|
|
41
|
+
* - Position: The position of the insertion. (Required)
|
|
42
|
+
* - Inserted symbols: The symbols that are inserted. (Required)
|
|
43
|
+
*
|
|
44
|
+
* Examples: `ins_S:614:G`, `ins_614:G`
|
|
45
|
+
*
|
|
46
|
+
* @fires {CustomEvent<{
|
|
47
|
+
* nucleotideMutations: string[],
|
|
48
|
+
* aminoAcidMutations: string[],
|
|
49
|
+
* nucleotideInsertions: string[],
|
|
50
|
+
* aminoAcidInsertions: string[]
|
|
51
|
+
* }>} gs-mutation-filter-changed
|
|
52
|
+
* Fired when:
|
|
53
|
+
* - The user has submitted a valid mutation or insertion
|
|
54
|
+
* - The user has removed a mutation or insertion
|
|
55
|
+
*
|
|
56
|
+
* @fires {CustomEvent<{
|
|
57
|
+
* nucleotideMutations: string[],
|
|
58
|
+
* aminoAcidMutations: string[],
|
|
59
|
+
* nucleotideInsertions: string[],
|
|
60
|
+
* aminoAcidInsertions: string[]
|
|
61
|
+
* }>} gs-mutation-filter-on-blur
|
|
62
|
+
* Fired when:
|
|
63
|
+
* - the mutation filter has lost focus
|
|
64
|
+
* Contains the selected mutations in the format
|
|
65
|
+
*/
|
|
66
|
+
@customElement('gs-mutation-filter')
|
|
67
|
+
export class MutationFilterComponent extends PreactLitAdapter {
|
|
68
|
+
// prettier-ignore
|
|
69
|
+
// The multiline union type must not start with `|` because it looks weird in the Storybook docs
|
|
70
|
+
/**
|
|
71
|
+
* The initial value to use for this mutation filter.
|
|
72
|
+
* All values provided must be valid mutations or insertions.
|
|
73
|
+
* Invalid values will be ignored.
|
|
74
|
+
*/
|
|
75
|
+
@property()
|
|
76
|
+
initialValue:
|
|
77
|
+
{
|
|
78
|
+
nucleotideMutations: string[];
|
|
79
|
+
aminoAcidMutations: string[];
|
|
80
|
+
nucleotideInsertions: string[];
|
|
81
|
+
aminoAcidInsertions: string[];
|
|
82
|
+
}
|
|
83
|
+
| string[]
|
|
84
|
+
| undefined = undefined;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The width of the component.
|
|
88
|
+
*
|
|
89
|
+
* Visit https://genspectrum.github.io/dashboards/?path=/docs/components-size-of-components--docs for more information.
|
|
90
|
+
*/
|
|
91
|
+
@property({ type: String })
|
|
92
|
+
width: string = '100%';
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The height of the component.
|
|
96
|
+
*
|
|
97
|
+
* Visit https://genspectrum.github.io/dashboards/?path=/docs/components-size-of-components--docs for more information.
|
|
98
|
+
*/
|
|
99
|
+
@property({ type: String })
|
|
100
|
+
height: string = '6.5rem';
|
|
101
|
+
|
|
102
|
+
override render() {
|
|
103
|
+
return (
|
|
104
|
+
<ReferenceGenomesAwaiter>
|
|
105
|
+
<MutationFilter initialValue={this.initialValue} width={this.width} height={this.height} />
|
|
106
|
+
</ReferenceGenomesAwaiter>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
declare global {
|
|
112
|
+
interface HTMLElementTagNameMap {
|
|
113
|
+
'gs-mutation-filter': MutationFilterComponent;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
interface HTMLElementEventMap {
|
|
117
|
+
'gs-mutation-filter-changed': CustomEvent<SelectedMutationFilterStrings>;
|
|
118
|
+
'gs-mutation-filter-on-blur': CustomEvent<SelectedMutationFilterStrings>;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* eslint-disable @typescript-eslint/no-unused-vars, no-unused-vars */
|
|
123
|
+
type InitialValueMatches = Expect<
|
|
124
|
+
Equals<typeof MutationFilterComponent.prototype.initialValue, MutationFilterProps['initialValue']>
|
|
125
|
+
>;
|
|
126
|
+
/* eslint-enable @typescript-eslint/no-unused-vars, no-unused-vars */
|
package/src/web-components/input/{text-input-component.stories.ts → gs-text-input.stories.ts}
RENAMED
|
@@ -6,15 +6,20 @@ import { html } from 'lit';
|
|
|
6
6
|
import { withComponentDocs } from '../../../.storybook/ComponentDocsBlock';
|
|
7
7
|
import { AGGREGATED_ENDPOINT, LAPIS_URL } from '../../constants';
|
|
8
8
|
import '../app';
|
|
9
|
-
import './text-input
|
|
9
|
+
import './gs-text-input';
|
|
10
10
|
import data from '../../preact/textInput/__mockData__/aggregated_hosts.json';
|
|
11
11
|
import type { TextInputProps } from '../../preact/textInput/text-input';
|
|
12
12
|
import { withinShadowRoot } from '../withinShadowRoot.story';
|
|
13
13
|
|
|
14
14
|
const codeExample = String.raw`
|
|
15
|
-
<gs-text-input
|
|
15
|
+
<gs-text-input
|
|
16
|
+
lapisField="host"
|
|
17
|
+
placeholderText="Enter host name"
|
|
18
|
+
initialValue="Homo sapiens"
|
|
19
|
+
width="50%">
|
|
20
|
+
</gs-text-input>`;
|
|
16
21
|
|
|
17
|
-
const meta: Meta<TextInputProps
|
|
22
|
+
const meta: Meta<Required<TextInputProps>> = {
|
|
18
23
|
title: 'Input/Text input',
|
|
19
24
|
component: 'gs-text-input',
|
|
20
25
|
parameters: withComponentDocs({
|
|
@@ -39,19 +44,40 @@ const meta: Meta<TextInputProps> = {
|
|
|
39
44
|
],
|
|
40
45
|
},
|
|
41
46
|
componentDocs: {
|
|
42
|
-
tag: 'gs-text-input',
|
|
43
47
|
opensShadowDom: true,
|
|
44
48
|
expectsChildren: false,
|
|
45
49
|
codeExample,
|
|
46
50
|
},
|
|
47
51
|
}),
|
|
52
|
+
argTypes: {
|
|
53
|
+
lapisField: {
|
|
54
|
+
control: {
|
|
55
|
+
type: 'text',
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
placeholderText: {
|
|
59
|
+
control: {
|
|
60
|
+
type: 'text',
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
initialValue: {
|
|
64
|
+
control: {
|
|
65
|
+
type: 'text',
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
width: {
|
|
69
|
+
control: {
|
|
70
|
+
type: 'text',
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
},
|
|
48
74
|
decorators: [withActions],
|
|
49
75
|
tags: ['autodocs'],
|
|
50
76
|
};
|
|
51
77
|
|
|
52
78
|
export default meta;
|
|
53
79
|
|
|
54
|
-
export const Default: StoryObj<TextInputProps
|
|
80
|
+
export const Default: StoryObj<Required<TextInputProps>> = {
|
|
55
81
|
render: (args) => {
|
|
56
82
|
return html` <gs-app lapis="${LAPIS_URL}">
|
|
57
83
|
<div class="max-w-screen-lg">
|
|
@@ -59,6 +85,7 @@ export const Default: StoryObj<TextInputProps> = {
|
|
|
59
85
|
.lapisField=${args.lapisField}
|
|
60
86
|
.placeholderText=${args.placeholderText}
|
|
61
87
|
.initialValue=${args.initialValue}
|
|
88
|
+
.width=${args.width}
|
|
62
89
|
></gs-text-input>
|
|
63
90
|
</div>
|
|
64
91
|
</gs-app>`;
|
|
@@ -67,10 +94,11 @@ export const Default: StoryObj<TextInputProps> = {
|
|
|
67
94
|
lapisField: 'host',
|
|
68
95
|
placeholderText: 'Enter host name',
|
|
69
96
|
initialValue: 'Homo sapiens',
|
|
97
|
+
width: '100%',
|
|
70
98
|
},
|
|
71
99
|
};
|
|
72
100
|
|
|
73
|
-
export const FiresEvent: StoryObj<TextInputProps
|
|
101
|
+
export const FiresEvent: StoryObj<Required<TextInputProps>> = {
|
|
74
102
|
...Default,
|
|
75
103
|
play: async ({ canvasElement, step }) => {
|
|
76
104
|
const canvas = await withinShadowRoot(canvasElement, 'gs-text-input');
|
|
@@ -7,7 +7,7 @@ import { PreactLitAdapter } from '../PreactLitAdapter';
|
|
|
7
7
|
*
|
|
8
8
|
* ## Context
|
|
9
9
|
*
|
|
10
|
-
* This component provides a text input field to specify filters for arbitrary fields of this
|
|
10
|
+
* This component provides a text input field to specify filters for arbitrary fields of this LAPIS instance.
|
|
11
11
|
*
|
|
12
12
|
* @fires {CustomEvent<Record<string, string>>} gs-text-input-changed
|
|
13
13
|
* Fired when the input field is changed.
|
|
@@ -25,10 +25,13 @@ export class TextInputComponent extends PreactLitAdapter {
|
|
|
25
25
|
* The initial value to use for this text input.
|
|
26
26
|
*/
|
|
27
27
|
@property()
|
|
28
|
-
initialValue: string
|
|
28
|
+
initialValue: string = '';
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
|
-
*
|
|
31
|
+
* Required.
|
|
32
|
+
*
|
|
33
|
+
* The LAPIS field name to use for this text input.
|
|
34
|
+
* The field must exist on this LAPIS instance.
|
|
32
35
|
*/
|
|
33
36
|
@property()
|
|
34
37
|
lapisField = '';
|
|
@@ -37,7 +40,15 @@ export class TextInputComponent extends PreactLitAdapter {
|
|
|
37
40
|
* The placeholder text to display in the input field.
|
|
38
41
|
*/
|
|
39
42
|
@property()
|
|
40
|
-
placeholderText: string
|
|
43
|
+
placeholderText: string = '';
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The width of the component.
|
|
47
|
+
*
|
|
48
|
+
* Visit https://genspectrum.github.io/dashboards/?path=/docs/components-size-of-components--docs for more information.
|
|
49
|
+
*/
|
|
50
|
+
@property({ type: String })
|
|
51
|
+
width: string = '100%';
|
|
41
52
|
|
|
42
53
|
override render() {
|
|
43
54
|
return (
|
|
@@ -45,6 +56,7 @@ export class TextInputComponent extends PreactLitAdapter {
|
|
|
45
56
|
lapisField={this.lapisField}
|
|
46
57
|
placeholderText={this.placeholderText}
|
|
47
58
|
initialValue={this.initialValue}
|
|
59
|
+
width={this.width}
|
|
48
60
|
/>
|
|
49
61
|
);
|
|
50
62
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { DateRangeSelectorComponent } from './date-range-selector
|
|
2
|
-
export { LocationFilterComponent } from './location-filter
|
|
3
|
-
export { TextInputComponent } from './text-input
|
|
4
|
-
export { MutationFilterComponent } from './mutation-filter
|
|
1
|
+
export { DateRangeSelectorComponent } from './gs-date-range-selector';
|
|
2
|
+
export { LocationFilterComponent } from './gs-location-filter';
|
|
3
|
+
export { TextInputComponent } from './gs-text-input';
|
|
4
|
+
export { MutationFilterComponent } from './gs-mutation-filter';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Meta } from '@storybook/blocks';
|
|
2
|
+
|
|
3
|
+
<Meta title='Input/Introduction' />
|
|
4
|
+
|
|
5
|
+
# Introduction
|
|
6
|
+
|
|
7
|
+
The components in this section let the user specify values for LAPIS filters.
|
|
8
|
+
The filters can then be used as input to the visualization components.
|
|
9
|
+
|
|
10
|
+
Every component fires `CustomEvent`s when the user interacts with it, which can be used to update the LAPIS filters.
|
|
11
|
+
The `detail` of the event is designed such that it can be directly passed to the LAPIS API.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Meta } from '@storybook/blocks';
|
|
2
|
+
|
|
3
|
+
<Meta title='Introduction' />
|
|
4
|
+
|
|
5
|
+
# Introduction
|
|
6
|
+
|
|
7
|
+
This package provides a collection of web components to build interactive dashboards that visualize
|
|
8
|
+
data of a specific instance of [LAPIS](https://github.com/GenSpectrum/LAPIS).
|
|
9
|
+
|
|
10
|
+
We primarily provide two kinds of components:
|
|
11
|
+
|
|
12
|
+
- Visualization components (charts, tables, etc.).
|
|
13
|
+
Those components fetch data from the LAPIS instance and visualize it.
|
|
14
|
+
- Input components that let you specify sequence filters for the LAPIS requests.
|
|
15
|
+
Input changes will fire events that can be listened to by the visualization components. It is the responsibility of the dashbaord maintainer to listen to those events and to wire the data correctly into the visualization components.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Meta } from '@storybook/blocks';
|
|
2
|
+
|
|
3
|
+
<Meta title='Visualization/Data Visualization and Statistical Analysis' />
|
|
4
|
+
|
|
5
|
+
# Data Visualization and Statistical Analysis
|
|
6
|
+
|
|
7
|
+
## Scales
|
|
8
|
+
|
|
9
|
+
On most plots, users can select the y-axis scaling through a dropdown.
|
|
10
|
+
They can choose between linear, logarithmic and logistic scaling.
|
|
11
|
+
By default, it is set to a linear scale.
|
|
12
|
+
|
|
13
|
+
In general, for each scale the displayed height of a value is calculated by applying the corresponding scale function.
|
|
14
|
+
|
|
15
|
+
- Linear: `value`
|
|
16
|
+
- Logarithmic: `ln(value)`
|
|
17
|
+
- Logistic: `ln(value / (1 - value))`
|
|
18
|
+
|
|
19
|
+
## Confidence Intervals
|
|
20
|
+
|
|
21
|
+
On bar and line plots, users can choose to display confidence intervals.
|
|
22
|
+
For line plots, this is done by shading the area between the upper and lower bounds.
|
|
23
|
+
For bar plots, this is done by adding error bars to the top of each bar.
|
|
24
|
+
|
|
25
|
+
Currently, only one method is available for calculating the confidence intervals:
|
|
26
|
+
the [wilson score interval](https://en.wikipedia.org/wiki/Binomial_proportion_confidence_interval#Wilson_score_interval) with a confidence level of 95%.
|
|
@@ -6,19 +6,30 @@ import { AGGREGATED_ENDPOINT, LAPIS_URL } from '../../constants';
|
|
|
6
6
|
import aggregatedData from '../../preact/aggregatedData/__mockData__/aggregated.json';
|
|
7
7
|
import type { AggregateProps } from '../../preact/aggregatedData/aggregate';
|
|
8
8
|
|
|
9
|
-
import './aggregate
|
|
9
|
+
import './gs-aggregate';
|
|
10
10
|
import '../app';
|
|
11
11
|
|
|
12
|
-
const
|
|
12
|
+
const codeExample = `
|
|
13
|
+
<gs-aggregate
|
|
14
|
+
fields='["division", "host"]'
|
|
15
|
+
filter='{"country": "USA"}'
|
|
16
|
+
views='["table"]'
|
|
17
|
+
headline="Aggregate"
|
|
18
|
+
width='100%'
|
|
19
|
+
height='700px'
|
|
20
|
+
></gs-aggregate>`;
|
|
21
|
+
|
|
22
|
+
const meta: Meta<Required<AggregateProps>> = {
|
|
13
23
|
title: 'Visualization/Aggregate',
|
|
14
|
-
component: 'gs-aggregate
|
|
24
|
+
component: 'gs-aggregate',
|
|
15
25
|
argTypes: {
|
|
16
26
|
fields: [{ control: 'object' }],
|
|
17
27
|
views: {
|
|
18
28
|
options: ['table'],
|
|
19
29
|
control: { type: 'check' },
|
|
20
30
|
},
|
|
21
|
-
|
|
31
|
+
width: { control: 'text' },
|
|
32
|
+
height: { control: 'text' },
|
|
22
33
|
headline: { control: 'text' },
|
|
23
34
|
},
|
|
24
35
|
parameters: withComponentDocs({
|
|
@@ -41,10 +52,9 @@ const meta: Meta<AggregateProps> = {
|
|
|
41
52
|
],
|
|
42
53
|
},
|
|
43
54
|
componentDocs: {
|
|
44
|
-
tag: 'gs-aggregate-component',
|
|
45
55
|
opensShadowDom: true,
|
|
46
56
|
expectsChildren: false,
|
|
47
|
-
codeExample
|
|
57
|
+
codeExample,
|
|
48
58
|
},
|
|
49
59
|
}),
|
|
50
60
|
tags: ['autodocs'],
|
|
@@ -52,16 +62,17 @@ const meta: Meta<AggregateProps> = {
|
|
|
52
62
|
|
|
53
63
|
export default meta;
|
|
54
64
|
|
|
55
|
-
export const Table: StoryObj<AggregateProps
|
|
65
|
+
export const Table: StoryObj<Required<AggregateProps>> = {
|
|
56
66
|
render: (args) => html`
|
|
57
67
|
<gs-app lapis="${LAPIS_URL}">
|
|
58
|
-
<gs-aggregate
|
|
68
|
+
<gs-aggregate
|
|
59
69
|
.fields=${args.fields}
|
|
60
70
|
.filter=${args.filter}
|
|
61
71
|
.views=${args.views}
|
|
62
|
-
.
|
|
72
|
+
.width=${args.width}
|
|
73
|
+
.height=${args.height}
|
|
63
74
|
.headline=${args.headline}
|
|
64
|
-
></gs-aggregate
|
|
75
|
+
></gs-aggregate>
|
|
65
76
|
</gs-app>
|
|
66
77
|
`,
|
|
67
78
|
args: {
|
|
@@ -70,7 +81,8 @@ export const Table: StoryObj<AggregateProps> = {
|
|
|
70
81
|
filter: {
|
|
71
82
|
country: 'USA',
|
|
72
83
|
},
|
|
73
|
-
|
|
84
|
+
width: '100%',
|
|
85
|
+
height: '700px',
|
|
74
86
|
headline: 'Aggregate',
|
|
75
87
|
},
|
|
76
88
|
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { customElement, property } from 'lit/decorators.js';
|
|
2
|
+
|
|
3
|
+
import { Aggregate, type View } from '../../preact/aggregatedData/aggregate';
|
|
4
|
+
import { type LapisFilter } from '../../types';
|
|
5
|
+
import { PreactLitAdapterWithGridJsStyles } from '../PreactLitAdapterWithGridJsStyles';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* ## Context
|
|
9
|
+
*
|
|
10
|
+
* This component displays aggregated data in a table, which can provide an overview of the underlying data.
|
|
11
|
+
*
|
|
12
|
+
* It expects a list of `fields` to aggregate by and a `filter` to apply to the data.
|
|
13
|
+
*
|
|
14
|
+
* ## Views
|
|
15
|
+
*
|
|
16
|
+
* ### Table View
|
|
17
|
+
*
|
|
18
|
+
* In the table view, the data is presented in a table format where each field is a column,
|
|
19
|
+
* along with the aggregated value and its proportion.
|
|
20
|
+
* The proportion represents the ratio of the aggregated value to the total count of the data
|
|
21
|
+
* (considering the applied filter).
|
|
22
|
+
*/
|
|
23
|
+
@customElement('gs-aggregate')
|
|
24
|
+
export class AggregateComponent extends PreactLitAdapterWithGridJsStyles {
|
|
25
|
+
/**
|
|
26
|
+
* The fields to aggregate by.
|
|
27
|
+
* Every field will be a table column.
|
|
28
|
+
* Every field must exist in the backing LAPIS instance.
|
|
29
|
+
*
|
|
30
|
+
* If left empty, the component will only show the absolute count of the provided `filter` and proportion `100%`.
|
|
31
|
+
*/
|
|
32
|
+
@property({ type: Array })
|
|
33
|
+
fields: string[] = [];
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A list of tabs with views that this component should provide.
|
|
37
|
+
*/
|
|
38
|
+
@property({ type: Array })
|
|
39
|
+
views: View[] = ['table'];
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The filter to apply to the data.
|
|
43
|
+
* It must be a valid LAPIS filter object.
|
|
44
|
+
*/
|
|
45
|
+
@property({ type: Object })
|
|
46
|
+
filter: LapisFilter = {};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The width of the component.
|
|
50
|
+
*
|
|
51
|
+
* Visit https://genspectrum.github.io/dashboards/?path=/docs/components-size-of-components--docs for more information.
|
|
52
|
+
*/
|
|
53
|
+
@property({ type: String })
|
|
54
|
+
width: string = '100%';
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The height of the component.
|
|
58
|
+
*
|
|
59
|
+
* Visit https://genspectrum.github.io/dashboards/?path=/docs/components-size-of-components--docs for more information.
|
|
60
|
+
*/
|
|
61
|
+
@property({ type: String })
|
|
62
|
+
height: string = '700px';
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The headline of the component. Set to an empty string to hide the headline.
|
|
66
|
+
*/
|
|
67
|
+
@property({ type: String })
|
|
68
|
+
headline: string = 'Aggregate';
|
|
69
|
+
|
|
70
|
+
override render() {
|
|
71
|
+
return (
|
|
72
|
+
<Aggregate
|
|
73
|
+
fields={this.fields}
|
|
74
|
+
views={this.views}
|
|
75
|
+
filter={this.filter}
|
|
76
|
+
width={this.width}
|
|
77
|
+
height={this.height}
|
|
78
|
+
headline={this.headline}
|
|
79
|
+
/>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
declare global {
|
|
85
|
+
interface HTMLElementTagNameMap {
|
|
86
|
+
'gs-aggregate-component': AggregateComponent;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -2,7 +2,7 @@ import { expect, fireEvent, waitFor } from '@storybook/test';
|
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/web-components';
|
|
3
3
|
import { html } from 'lit';
|
|
4
4
|
|
|
5
|
-
import './mutation-comparison
|
|
5
|
+
import './gs-mutation-comparison';
|
|
6
6
|
import '../app';
|
|
7
7
|
import { withComponentDocs } from '../../../.storybook/ComponentDocsBlock';
|
|
8
8
|
import { LAPIS_URL, NUCLEOTIDE_MUTATIONS_ENDPOINT } from '../../constants';
|
|
@@ -12,15 +12,18 @@ import { type MutationComparisonProps } from '../../preact/mutationComparison/mu
|
|
|
12
12
|
import { withinShadowRoot } from '../withinShadowRoot.story';
|
|
13
13
|
|
|
14
14
|
const codeExample = String.raw`
|
|
15
|
-
<gs-mutation-comparison
|
|
15
|
+
<gs-mutation-comparison
|
|
16
16
|
variants='[{ "displayName": "variant1", "lapisFilter": { "country": "Switzerland" }}, { "displayName": "variant2", "lapisFilter": { "country": "Germany" }}]'
|
|
17
17
|
sequenceType="nucleotide"
|
|
18
18
|
views='["table", "venn"]'
|
|
19
|
-
|
|
19
|
+
headline="Mutation comparison"
|
|
20
|
+
width='100%'
|
|
21
|
+
height='700px'
|
|
22
|
+
></gs-mutation-comparison>`;
|
|
20
23
|
|
|
21
|
-
const meta: Meta<MutationComparisonProps
|
|
24
|
+
const meta: Meta<Required<MutationComparisonProps>> = {
|
|
22
25
|
title: 'Visualization/Mutation comparison',
|
|
23
|
-
component: 'gs-mutation-comparison
|
|
26
|
+
component: 'gs-mutation-comparison',
|
|
24
27
|
argTypes: {
|
|
25
28
|
variants: { control: 'object' },
|
|
26
29
|
sequenceType: {
|
|
@@ -31,12 +34,12 @@ const meta: Meta<MutationComparisonProps> = {
|
|
|
31
34
|
options: ['table', 'venn'],
|
|
32
35
|
control: { type: 'check' },
|
|
33
36
|
},
|
|
34
|
-
|
|
37
|
+
width: { control: 'text' },
|
|
38
|
+
height: { control: 'text' },
|
|
35
39
|
headline: { control: 'text' },
|
|
36
40
|
},
|
|
37
41
|
parameters: withComponentDocs({
|
|
38
42
|
componentDocs: {
|
|
39
|
-
tag: 'gs-mutation-comparison-component',
|
|
40
43
|
opensShadowDom: true,
|
|
41
44
|
expectsChildren: false,
|
|
42
45
|
codeExample,
|
|
@@ -47,16 +50,17 @@ const meta: Meta<MutationComparisonProps> = {
|
|
|
47
50
|
|
|
48
51
|
export default meta;
|
|
49
52
|
|
|
50
|
-
const Template: StoryObj<MutationComparisonProps
|
|
53
|
+
const Template: StoryObj<Required<MutationComparisonProps>> = {
|
|
51
54
|
render: (args) => html`
|
|
52
55
|
<gs-app lapis="${LAPIS_URL}">
|
|
53
|
-
<gs-mutation-comparison
|
|
56
|
+
<gs-mutation-comparison
|
|
54
57
|
.variants=${args.variants}
|
|
55
58
|
.sequenceType=${args.sequenceType}
|
|
56
59
|
.views=${args.views}
|
|
57
|
-
.
|
|
60
|
+
.width=${args.width}
|
|
61
|
+
.height=${args.height}
|
|
58
62
|
.headline=${args.headline}
|
|
59
|
-
></gs-mutation-comparison
|
|
63
|
+
></gs-mutation-comparison>
|
|
60
64
|
</gs-app>
|
|
61
65
|
`,
|
|
62
66
|
};
|
|
@@ -64,7 +68,7 @@ const Template: StoryObj<MutationComparisonProps> = {
|
|
|
64
68
|
const dateTo = '2022-01-01';
|
|
65
69
|
const dateFrom = '2021-01-01';
|
|
66
70
|
|
|
67
|
-
export const Default: StoryObj<MutationComparisonProps
|
|
71
|
+
export const Default: StoryObj<Required<MutationComparisonProps>> = {
|
|
68
72
|
...Template,
|
|
69
73
|
args: {
|
|
70
74
|
variants: [
|
|
@@ -84,7 +88,8 @@ export const Default: StoryObj<MutationComparisonProps> = {
|
|
|
84
88
|
],
|
|
85
89
|
sequenceType: 'nucleotide',
|
|
86
90
|
views: ['table', 'venn'],
|
|
87
|
-
|
|
91
|
+
width: '100%',
|
|
92
|
+
height: '700px',
|
|
88
93
|
headline: 'Mutation comparison',
|
|
89
94
|
},
|
|
90
95
|
parameters: {
|
|
@@ -127,7 +132,7 @@ export const Default: StoryObj<MutationComparisonProps> = {
|
|
|
127
132
|
},
|
|
128
133
|
},
|
|
129
134
|
play: async ({ canvasElement, step }) => {
|
|
130
|
-
const canvas = await withinShadowRoot(canvasElement, 'gs-mutation-comparison
|
|
135
|
+
const canvas = await withinShadowRoot(canvasElement, 'gs-mutation-comparison');
|
|
131
136
|
|
|
132
137
|
await step('Min and max proportions should be 50% and 100%', async () => {
|
|
133
138
|
const minInput = () => canvas.getAllByLabelText('%')[0];
|
|
@@ -139,10 +144,10 @@ export const Default: StoryObj<MutationComparisonProps> = {
|
|
|
139
144
|
},
|
|
140
145
|
};
|
|
141
146
|
|
|
142
|
-
export const VennDiagram: StoryObj<MutationComparisonProps
|
|
147
|
+
export const VennDiagram: StoryObj<Required<MutationComparisonProps>> = {
|
|
143
148
|
...Default,
|
|
144
149
|
play: async ({ canvasElement, step }) => {
|
|
145
|
-
const canvas = await withinShadowRoot(canvasElement, 'gs-mutation-comparison
|
|
150
|
+
const canvas = await withinShadowRoot(canvasElement, 'gs-mutation-comparison');
|
|
146
151
|
|
|
147
152
|
await step('Switch to Venn diagram view', async () => {
|
|
148
153
|
await waitFor(() => expect(canvas.getByRole('button', { name: 'Venn' })).toBeInTheDocument());
|