@genspectrum/dashboard-components 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/custom-elements.json +488 -117
  2. package/dist/dashboard-components.js +904 -466
  3. package/dist/dashboard-components.js.map +1 -1
  4. package/dist/genspectrum-components.d.ts +473 -67
  5. package/dist/style.css +273 -153
  6. package/package.json +11 -7
  7. package/src/preact/aggregatedData/aggregate.stories.tsx +7 -5
  8. package/src/preact/aggregatedData/aggregate.tsx +16 -7
  9. package/src/preact/components/ReferenceGenomesAwaiter.tsx +25 -0
  10. package/src/preact/components/csv-download-button.tsx +8 -2
  11. package/src/preact/components/headline.stories.tsx +19 -1
  12. package/src/preact/components/headline.tsx +25 -5
  13. package/src/preact/components/info.stories.tsx +24 -3
  14. package/src/preact/components/info.tsx +49 -5
  15. package/src/preact/components/min-max-range-slider.tsx +4 -4
  16. package/src/preact/components/percent-intput.tsx +2 -3
  17. package/src/preact/components/resize-container.tsx +23 -0
  18. package/src/preact/components/table.tsx +1 -0
  19. package/src/preact/components/tabs.stories.tsx +2 -2
  20. package/src/preact/components/tabs.tsx +47 -24
  21. package/src/preact/dateRangeSelector/date-range-selector.stories.tsx +36 -4
  22. package/src/preact/dateRangeSelector/date-range-selector.tsx +67 -53
  23. package/src/preact/locationFilter/location-filter.tsx +2 -2
  24. package/src/preact/mutationComparison/getMutationComparisonTableData.spec.ts +5 -5
  25. package/src/preact/mutationComparison/getMutationComparisonTableData.ts +45 -10
  26. package/src/preact/mutationComparison/mutation-comparison-table.tsx +20 -22
  27. package/src/preact/mutationComparison/mutation-comparison-venn.tsx +6 -3
  28. package/src/preact/mutationComparison/mutation-comparison.stories.tsx +11 -1
  29. package/src/preact/mutationComparison/mutation-comparison.tsx +16 -7
  30. package/src/preact/mutationFilter/mutation-filter.stories.tsx +70 -31
  31. package/src/preact/mutationFilter/mutation-filter.tsx +62 -14
  32. package/src/preact/mutations/getInsertionsTableData.spec.ts +6 -4
  33. package/src/preact/mutations/getInsertionsTableData.ts +1 -1
  34. package/src/preact/mutations/getMutationsTableData.spec.ts +9 -19
  35. package/src/preact/mutations/getMutationsTableData.ts +1 -1
  36. package/src/preact/mutations/mutations-insertions-table.tsx +3 -1
  37. package/src/preact/mutations/mutations-table.tsx +3 -1
  38. package/src/preact/mutations/mutations.stories.tsx +11 -1
  39. package/src/preact/mutations/mutations.tsx +24 -7
  40. package/src/preact/prevalenceOverTime/prevalence-over-time-bar-chart.tsx +1 -0
  41. package/src/preact/prevalenceOverTime/prevalence-over-time-bubble-chart.tsx +1 -0
  42. package/src/preact/prevalenceOverTime/prevalence-over-time-line-chart.tsx +1 -0
  43. package/src/preact/prevalenceOverTime/prevalence-over-time.stories.tsx +8 -0
  44. package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +31 -13
  45. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage-chart.tsx +8 -5
  46. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.stories.tsx +15 -0
  47. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +62 -12
  48. package/src/preact/shared/sort/sortInsertions.spec.ts +11 -10
  49. package/src/preact/shared/sort/sortInsertions.ts +10 -17
  50. package/src/preact/shared/sort/sortSubstitutionsAndDeletions.spec.ts +19 -10
  51. package/src/preact/shared/sort/sortSubstitutionsAndDeletions.ts +45 -12
  52. package/src/preact/textInput/text-input.stories.tsx +22 -1
  53. package/src/preact/textInput/text-input.tsx +3 -1
  54. package/src/utils/typeAssertions.spec.ts +31 -0
  55. package/src/utils/typeAssertions.ts +16 -0
  56. package/src/web-components/PreactLitAdapter.tsx +0 -1
  57. package/src/web-components/app.stories.ts +129 -0
  58. package/src/web-components/app.ts +27 -6
  59. package/src/web-components/display/aggregate-component.stories.ts +24 -11
  60. package/src/web-components/display/aggregate-component.tsx +26 -5
  61. package/src/web-components/display/mutation-comparison-component.stories.ts +32 -11
  62. package/src/web-components/display/mutation-comparison-component.tsx +79 -4
  63. package/src/web-components/display/mutations-component.stories.ts +40 -19
  64. package/src/web-components/display/mutations-component.tsx +71 -4
  65. package/src/web-components/display/prevalence-over-time-component.stories.ts +44 -18
  66. package/src/web-components/display/prevalence-over-time-component.tsx +105 -5
  67. package/src/web-components/display/relative-growth-advantage-component.stories.ts +32 -10
  68. package/src/web-components/display/relative-growth-advantage-component.tsx +66 -3
  69. package/src/web-components/input/date-range-selector-component.stories.ts +51 -9
  70. package/src/web-components/input/date-range-selector-component.tsx +69 -4
  71. package/src/web-components/input/location-filter-component.stories.ts +15 -4
  72. package/src/web-components/input/location-filter-component.tsx +2 -6
  73. package/src/web-components/input/mutation-filter-component.stories.ts +33 -12
  74. package/src/web-components/input/mutation-filter-component.tsx +60 -4
  75. package/src/web-components/input/text-input-component.stories.ts +26 -6
  76. package/src/web-components/input/text-input-component.tsx +34 -3
  77. package/src/web-components/display/aggregate-component.mdx +0 -25
  78. package/src/web-components/input/location-filter.mdx +0 -25
@@ -2,21 +2,82 @@ import { customElement, property } from 'lit/decorators.js';
2
2
 
3
3
  import { Mutations, type View } from '../../preact/mutations/mutations';
4
4
  import { type LapisFilter, type SequenceType } from '../../types';
5
+ import type { Equals, Expect } from '../../utils/typeAssertions';
5
6
  import { PreactLitAdapterWithGridJsStyles } from '../PreactLitAdapterWithGridJsStyles';
6
7
 
8
+ /**
9
+ * This component displays mutations (substitutions, deletions and insertions) for a given variant.
10
+ *
11
+ * ## Views
12
+ *
13
+ * ### Table View
14
+ *
15
+ * The table view shows all substitutions and deletions for the given variant.
16
+ * It shows the type (substitution or deletion), the total count of the mutation
17
+ * and the proportion of the mutation in the variant.
18
+ * The proportion is relative to the total number of sequences matching
19
+ * the specified sequence filters with non-ambiguous reads at that position.
20
+ *
21
+ * The proportion interval filter can be used to filter the displayed mutations on client side.
22
+ *
23
+ * ### Grid View
24
+ *
25
+ * The grid view shows the proportion of each sequence symbol (nucleotide or amino acid) for each position that has a mutation.
26
+ * Only positions with at least one mutation in the selected proportion interval are shown.
27
+ *
28
+ * ### Insertions View
29
+ *
30
+ * The insertions view shows the count of all insertions for the given variant.
31
+ *
32
+ */
7
33
  @customElement('gs-mutations-component')
8
34
  export class MutationsComponent extends PreactLitAdapterWithGridJsStyles {
35
+ /**
36
+ * The `variant` will be sent as is to LAPIS to filter the mutation data.
37
+ * It must be a valid LAPIS filter object.
38
+ */
9
39
  @property({ type: Object })
10
- variant: LapisFilter = { displayName: '' };
40
+ variant: Record<string, string | number | null | boolean> = { displayName: '' };
11
41
 
42
+ /**
43
+ * The type of the sequence for which the mutations should be shown.
44
+ */
12
45
  @property({ type: String })
13
- sequenceType: SequenceType = 'nucleotide';
46
+ sequenceType: 'nucleotide' | 'amino acid' = 'nucleotide';
14
47
 
48
+ /**
49
+ * A list of tabs with views that this component should provide.
50
+ */
15
51
  @property({ type: Array })
16
- views: View[] = ['table', 'grid'];
52
+ views: ('table' | 'grid' | 'insertions')[] = ['table', 'grid'];
53
+
54
+ /**
55
+ * The size of the component.
56
+ *
57
+ * If not set, the component will take the full width of its container with height 700px.
58
+ *
59
+ * The width and height should be a string with a unit in css style, e.g. '100%', '500px' or '50vh'.
60
+ * If the unit is %, the size will be relative to the container of the component.
61
+ */
62
+ @property({ type: Object })
63
+ size: { width?: string; height?: string } | undefined = undefined;
64
+
65
+ /**
66
+ * The headline of the component. Set to an empty string to hide the headline.
67
+ */
68
+ @property({ type: String })
69
+ headline: string | undefined = 'Mutations';
17
70
 
18
71
  override render() {
19
- return <Mutations variant={this.variant} sequenceType={this.sequenceType} views={this.views} />;
72
+ return (
73
+ <Mutations
74
+ variant={this.variant}
75
+ sequenceType={this.sequenceType}
76
+ views={this.views}
77
+ size={this.size}
78
+ headline={this.headline}
79
+ />
80
+ );
20
81
  }
21
82
  }
22
83
 
@@ -25,3 +86,9 @@ declare global {
25
86
  'gs-mutations-component': MutationsComponent;
26
87
  }
27
88
  }
89
+
90
+ /* eslint-disable @typescript-eslint/no-unused-vars, no-unused-vars */
91
+ type VariantsMatches = Expect<Equals<typeof MutationsComponent.prototype.variant, LapisFilter>>;
92
+ type SequenceTypeMatches = Expect<Equals<typeof MutationsComponent.prototype.sequenceType, SequenceType>>;
93
+ type ViewsMatches = Expect<Equals<typeof MutationsComponent.prototype.views, View[]>>;
94
+ /* eslint-enable @typescript-eslint/no-unused-vars, no-unused-vars */
@@ -4,6 +4,7 @@ import { html } from 'lit';
4
4
 
5
5
  import '../app';
6
6
  import './prevalence-over-time-component';
7
+ import { withComponentDocs } from '../../../.storybook/ComponentDocsBlock';
7
8
  import { AGGREGATED_ENDPOINT, LAPIS_URL } from '../../constants';
8
9
  import denominator from '../../preact/prevalenceOverTime/__mockData__/denominator.json';
9
10
  import denominatorOneVariant from '../../preact/prevalenceOverTime/__mockData__/denominatorOneVariant.json';
@@ -13,6 +14,16 @@ import numeratorOneVariant from '../../preact/prevalenceOverTime/__mockData__/nu
13
14
  import { type PrevalenceOverTimeProps } from '../../preact/prevalenceOverTime/prevalence-over-time';
14
15
  import { withinShadowRoot } from '../withinShadowRoot.story';
15
16
 
17
+ const codeExample = String.raw`
18
+ <gs-prevalence-over-time
19
+ numerator='[{ "displayName": "EG", "country": "USA", "pangoLineage": "EG*" }, { "displayName": "JN.1", "country": "USA", "pangoLineage": "JN.1*" }]'
20
+ denominator='{ "country": "USA", "displayName": "All" }'
21
+ granularity="month"
22
+ smoothingWindow="0"
23
+ views='["bar", "line", "bubble", "table"]'
24
+ confidenceIntervalMethods='["wilson"]'
25
+ ></gs-prevalence-over-time>`;
26
+
16
27
  const meta: Meta<PrevalenceOverTimeProps> = {
17
28
  title: 'Visualization/Prevalence over time',
18
29
  component: 'gs-prevalence-over-time',
@@ -32,25 +43,36 @@ const meta: Meta<PrevalenceOverTimeProps> = {
32
43
  options: ['wilson'],
33
44
  control: { type: 'check' },
34
45
  },
46
+ size: [{ control: 'object' }],
47
+ headline: { control: 'text' },
35
48
  },
49
+ parameters: withComponentDocs({
50
+ componentDocs: {
51
+ tag: 'gs-prevalence-over-time',
52
+ opensShadowDom: true,
53
+ expectsChildren: false,
54
+ codeExample,
55
+ },
56
+ }),
57
+ tags: ['autodocs'],
36
58
  };
37
59
 
38
60
  export default meta;
39
61
 
40
62
  const Template: StoryObj<PrevalenceOverTimeProps> = {
41
63
  render: (args) => html`
42
- <div class="w-11/12 h-11/12">
43
- <gs-app lapis="${LAPIS_URL}">
44
- <gs-prevalence-over-time
45
- .numerator=${args.numerator}
46
- .denominator=${args.denominator}
47
- .granularity=${args.granularity}
48
- .smoothingWindow=${args.smoothingWindow}
49
- .views=${args.views}
50
- .confidenceIntervalMethods=${args.confidenceIntervalMethods}
51
- ></gs-prevalence-over-time>
52
- </gs-app>
53
- </div>
64
+ <gs-app lapis="${LAPIS_URL}">
65
+ <gs-prevalence-over-time
66
+ .numerator=${args.numerator}
67
+ .denominator=${args.denominator}
68
+ .granularity=${args.granularity}
69
+ .smoothingWindow=${args.smoothingWindow}
70
+ .views=${args.views}
71
+ .confidenceIntervalMethods=${args.confidenceIntervalMethods}
72
+ .size=${args.size}
73
+ .headline=${args.headline}
74
+ ></gs-prevalence-over-time>
75
+ </gs-app>
54
76
  `,
55
77
  };
56
78
 
@@ -66,6 +88,8 @@ export const TwoVariants: StoryObj<PrevalenceOverTimeProps> = {
66
88
  smoothingWindow: 0,
67
89
  views: ['bar', 'line', 'bubble', 'table'],
68
90
  confidenceIntervalMethods: ['wilson'],
91
+ size: { width: '100%', height: '700px' },
92
+ headline: 'Prevalence over time',
69
93
  },
70
94
  parameters: {
71
95
  fetchMock: {
@@ -131,6 +155,8 @@ export const OneVariant: StoryObj<PrevalenceOverTimeProps> = {
131
155
  smoothingWindow: 7,
132
156
  views: ['bar', 'line', 'bubble', 'table'],
133
157
  confidenceIntervalMethods: ['wilson'],
158
+ size: { width: '100%', height: '700px' },
159
+ headline: 'Prevalence over time',
134
160
  },
135
161
  parameters: {
136
162
  fetchMock: {
@@ -176,9 +202,9 @@ export const OneVariantOnLineTab: StoryObj<PrevalenceOverTimeProps> = {
176
202
  play: async ({ canvasElement }) => {
177
203
  const canvas = await withinShadowRoot(canvasElement, 'gs-prevalence-over-time');
178
204
 
179
- await waitFor(() => expect(canvas.getByLabelText('Line', { selector: 'input' })).toBeInTheDocument());
205
+ await waitFor(() => expect(canvas.getByRole('button', { name: 'Line' })).toBeInTheDocument());
180
206
 
181
- await fireEvent.click(canvas.getByLabelText('Line', { selector: 'input' }));
207
+ await fireEvent.click(canvas.getByRole('button', { name: 'Line' }));
182
208
  },
183
209
  };
184
210
 
@@ -187,9 +213,9 @@ export const OneVariantOnBubbleTab: StoryObj<PrevalenceOverTimeProps> = {
187
213
  play: async ({ canvasElement }) => {
188
214
  const canvas = await withinShadowRoot(canvasElement, 'gs-prevalence-over-time');
189
215
 
190
- await waitFor(() => expect(canvas.getByLabelText('Bubble', { selector: 'input' })).toBeInTheDocument());
216
+ await waitFor(() => expect(canvas.getByRole('button', { name: 'Bubble' })).toBeInTheDocument());
191
217
 
192
- await fireEvent.click(canvas.getByLabelText('Bubble', { selector: 'input' }));
218
+ await fireEvent.click(canvas.getByRole('button', { name: 'Bubble' }));
193
219
  },
194
220
  };
195
221
 
@@ -198,8 +224,8 @@ export const OneVariantOnTableTab: StoryObj<PrevalenceOverTimeProps> = {
198
224
  play: async ({ canvasElement }) => {
199
225
  const canvas = await withinShadowRoot(canvasElement, 'gs-prevalence-over-time');
200
226
 
201
- await waitFor(() => expect(canvas.getByLabelText('Table', { selector: 'input' })).toBeInTheDocument());
227
+ await waitFor(() => expect(canvas.getByRole('button', { name: 'Table' })).toBeInTheDocument());
202
228
 
203
- await fireEvent.click(canvas.getByLabelText('Table', { selector: 'input' }));
229
+ await fireEvent.click(canvas.getByRole('button', { name: 'Table' }));
204
230
  },
205
231
  };
@@ -3,27 +3,113 @@ import { customElement, property } from 'lit/decorators.js';
3
3
  import PrevalenceOverTime, { type View } from '../../preact/prevalenceOverTime/prevalence-over-time';
4
4
  import { type ConfidenceIntervalMethod } from '../../preact/shared/charts/confideceInterval';
5
5
  import { type NamedLapisFilter, type TemporalGranularity } from '../../types';
6
+ import { type Equals, type Expect } from '../../utils/typeAssertions';
6
7
  import { PreactLitAdapterWithGridJsStyles } from '../PreactLitAdapterWithGridJsStyles';
7
8
 
9
+ /**
10
+ * This component displays the prevalence over time of one or more variants.
11
+ * The prevalence is calculated as the ratio of the number of cases of each variant given as `numerator`
12
+ * to the number of cases of the variant given as `denominator`.
13
+ *
14
+ * In the chart views,
15
+ * - the user can select whether to display a confidence interval (not available in the bubble chart).
16
+ * The confidence interval is calculated using [Wilson score interval](https://en.wikipedia.org/wiki/Binomial_proportion_confidence_interval),
17
+ * with a confidence level of 95%.
18
+ * - the x-axis shows time steps in the selected `granularity`.
19
+ * - the user can select the y-axis scale (linear, logistic, logit).
20
+ *
21
+ * ## Views
22
+ *
23
+ * ### Bar View
24
+ *
25
+ * Displays the prevalence over time as a bar chart.
26
+ * Shows a bar for each variant in the `numerator` on every time step.
27
+ *
28
+ * ### Line View
29
+ *
30
+ * Displays the prevalence over time as a line chart.
31
+ * Each data point is connected for better visibility.
32
+ * Shows a line for each variant in the `numerator`.
33
+ *
34
+ * ### Bubble View
35
+ *
36
+ * Displays the prevalence over time as a bubble chart.
37
+ * The size of the bubbles represents the number of cases of the `denominator` variant.
38
+ * The height of the bubbles represents the prevalence of the `numerator` variants.
39
+ *
40
+ * ### Table View
41
+ *
42
+ * Displays the prevalence over time as a table with one row per time point.
43
+ */
8
44
  @customElement('gs-prevalence-over-time')
9
45
  export class PrevalenceOverTimeComponent extends PreactLitAdapterWithGridJsStyles {
46
+ /**
47
+ * Either a single variant or an array of variants to compare.
48
+ * This must be a valid LAPIS filter object with an additional `displayName` property
49
+ * which will be used as the label for the variant in the views,
50
+ * or an array of such objects.
51
+ */
10
52
  @property({ type: Object })
11
- numerator: NamedLapisFilter | NamedLapisFilter[] = { displayName: '' };
53
+ numerator:
54
+ | (Record<string, string | number | null | boolean> & { displayName: string })
55
+ | (Record<string, string | number | null | boolean> & {
56
+ displayName: string;
57
+ })[] = { displayName: '' };
12
58
 
59
+ /**
60
+ * The variant that the variants in `numerator` are compared to.
61
+ */
13
62
  @property({ type: Object })
14
- denominator: NamedLapisFilter = { displayName: '' };
63
+ denominator: Record<string, string | number | null | boolean> & { displayName: string } = { displayName: '' };
15
64
 
65
+ /**
66
+ * The granularity of the time axis.
67
+ */
16
68
  @property({ type: String })
17
- granularity: TemporalGranularity = 'day';
69
+ granularity: 'day' | 'week' | 'month' | 'year' = 'day';
18
70
 
71
+ /**
72
+ * The number of time steps to use for smoothing the data.
73
+ * `0` means no smoothing.
74
+ * Must be a non-negative integer.
75
+ *
76
+ * For a given time, the shown value is the mean of the neighbouring measured values.
77
+ * The `smoothingWindow` value provides the number of neighbouring values to take into account.
78
+ * The resulting time is computed via `Math.floor(smoothingWindow / 2)`.
79
+ */
19
80
  @property({ type: Number })
20
81
  smoothingWindow: number = 0;
21
82
 
83
+ /**
84
+ * A list of tabs with views that this component should provide.
85
+ */
22
86
  @property({ type: Array })
23
- views: View[] = ['bar', 'line', 'bubble', 'table'];
87
+ views: ('bar' | 'line' | 'bubble' | 'table')[] = ['bar', 'line', 'bubble', 'table'];
24
88
 
89
+ /**
90
+ * A list of methods to calculate the confidence interval.
91
+ * The option `none` is always available and disables confidence intervals.
92
+ * Pass an empty array to disable the confidence interval selector.
93
+ */
25
94
  @property({ type: Array })
26
- confidenceIntervalMethods: ConfidenceIntervalMethod[] = ['wilson'];
95
+ confidenceIntervalMethods: ('wilson' | 'none')[] = ['wilson'];
96
+
97
+ /**
98
+ * The headline of the component. Set to an empty string to hide the headline.
99
+ */
100
+ @property({ type: String })
101
+ headline: string | undefined = 'Prevalence over time';
102
+
103
+ /**
104
+ * The size of the component.
105
+ *
106
+ * If not set, the component will take the full width of its container with height 700px.
107
+ *
108
+ * The width and height should be a string with a unit in css style, e.g. '100%', '500px' or '50vh'.
109
+ * If the unit is %, the size will be relative to the container of the component.
110
+ */
111
+ @property({ type: Object })
112
+ size: { width?: string; height?: string } | undefined = undefined;
27
113
 
28
114
  override render() {
29
115
  return (
@@ -34,6 +120,8 @@ export class PrevalenceOverTimeComponent extends PreactLitAdapterWithGridJsStyle
34
120
  smoothingWindow={this.smoothingWindow}
35
121
  views={this.views}
36
122
  confidenceIntervalMethods={this.confidenceIntervalMethods}
123
+ size={this.size}
124
+ headline={this.headline}
37
125
  />
38
126
  );
39
127
  }
@@ -44,3 +132,15 @@ declare global {
44
132
  'gs-prevalence-over-time': PrevalenceOverTimeComponent;
45
133
  }
46
134
  }
135
+
136
+ /* eslint-disable @typescript-eslint/no-unused-vars, no-unused-vars */
137
+ type NumeratorMatches = Expect<
138
+ Equals<typeof PrevalenceOverTimeComponent.prototype.numerator, NamedLapisFilter | NamedLapisFilter[]>
139
+ >;
140
+ type DenominatorMatches = Expect<Equals<typeof PrevalenceOverTimeComponent.prototype.denominator, NamedLapisFilter>>;
141
+ type GranularityMatches = Expect<Equals<typeof PrevalenceOverTimeComponent.prototype.granularity, TemporalGranularity>>;
142
+ type ViewsMatches = Expect<Equals<typeof PrevalenceOverTimeComponent.prototype.views, View[]>>;
143
+ type ConfidenceIntervalMethodsMatches = Expect<
144
+ Equals<typeof PrevalenceOverTimeComponent.prototype.confidenceIntervalMethods, ConfidenceIntervalMethod[]>
145
+ >;
146
+ /* eslint-enable @typescript-eslint/no-unused-vars, no-unused-vars */
@@ -3,11 +3,20 @@ import { html } from 'lit';
3
3
 
4
4
  import './relative-growth-advantage-component';
5
5
  import '../app';
6
+ import { withComponentDocs } from '../../../.storybook/ComponentDocsBlock';
6
7
  import { AGGREGATED_ENDPOINT, LAPIS_URL } from '../../constants';
7
8
  import denominator from '../../preact/relativeGrowthAdvantage/__mockData__/denominator.json';
8
9
  import numerator from '../../preact/relativeGrowthAdvantage/__mockData__/numerator.json';
9
10
  import { type RelativeGrowthAdvantageProps } from '../../preact/relativeGrowthAdvantage/relative-growth-advantage';
10
11
 
12
+ const codeExample = String.raw`
13
+ <gs-relative-growth-advantage
14
+ numerator='{ "country": "Switzerland", "pangoLineage": "B.1.1.7", "dateFrom": "2020-12-01" }'
15
+ denominator='{ "country": "Switzerland", "dateFrom": "2020-12-01" }'
16
+ generationTime="7"
17
+ views='["line"]'
18
+ ></gs-relative-growth-advantage>`;
19
+
11
20
  const meta: Meta<RelativeGrowthAdvantageProps> = {
12
21
  title: 'Visualization/Relative growth advantage',
13
22
  component: 'gs-relative-growth-advantage',
@@ -19,23 +28,34 @@ const meta: Meta<RelativeGrowthAdvantageProps> = {
19
28
  options: ['line'],
20
29
  control: { type: 'check' },
21
30
  },
31
+ size: [{ control: 'object' }],
32
+ headline: { control: 'text' },
22
33
  },
34
+ parameters: withComponentDocs({
35
+ componentDocs: {
36
+ tag: 'gs-relative-growth-advantage',
37
+ opensShadowDom: true,
38
+ expectsChildren: false,
39
+ codeExample,
40
+ },
41
+ }),
42
+ tags: ['autodocs'],
23
43
  };
24
44
 
25
45
  export default meta;
26
46
 
27
47
  const Template: StoryObj<RelativeGrowthAdvantageProps> = {
28
48
  render: (args) => html`
29
- <div class="w-11/12 h-11/12">
30
- <gs-app lapis="${LAPIS_URL}">
31
- <gs-relative-growth-advantage
32
- .numerator=${args.numerator}
33
- .denominator=${args.denominator}
34
- .generationTime=${args.generationTime}
35
- .views=${args.views}
36
- ></gs-relative-growth-advantage>
37
- </gs-app>
38
- </div>
49
+ <gs-app lapis="${LAPIS_URL}">
50
+ <gs-relative-growth-advantage
51
+ .numerator=${args.numerator}
52
+ .denominator=${args.denominator}
53
+ .generationTime=${args.generationTime}
54
+ .views=${args.views}
55
+ .size=${args.size}
56
+ .headline=${args.headline}
57
+ ></gs-relative-growth-advantage>
58
+ </gs-app>
39
59
  `,
40
60
  };
41
61
 
@@ -46,6 +66,8 @@ export const Default: StoryObj<RelativeGrowthAdvantageProps> = {
46
66
  denominator: { country: 'Switzerland', dateFrom: '2020-12-01', dateTo: '2021-03-01' },
47
67
  generationTime: 7,
48
68
  views: ['line'],
69
+ size: { width: '100%', height: '700px' },
70
+ headline: 'Relative growth advantage',
49
71
  },
50
72
  parameters: {
51
73
  fetchMock: {
@@ -2,21 +2,76 @@ import { customElement, property } from 'lit/decorators.js';
2
2
 
3
3
  import { RelativeGrowthAdvantage, type View } from '../../preact/relativeGrowthAdvantage/relative-growth-advantage';
4
4
  import type { LapisFilter } from '../../types';
5
+ import { type Equals, type Expect } from '../../utils/typeAssertions';
5
6
  import { PreactLitAdapter } from '../PreactLitAdapter';
6
7
 
8
+ /**
9
+ * We assume a discrete time model, where new infections happen exactly every `generationTime` days.
10
+ * This is what we call a "generation".
11
+ *
12
+ * This component estimates the relative growth advantage of a variant by performing a logistic regression.
13
+ * Based on the inferred logistic growth rate, we derive the relative growth advantage (per generation).
14
+ *
15
+ * For details on the scientific method, see:
16
+ * Chen, Chaoran, et al. "Quantification of the spread of SARS-CoV-2 variant B.1.1.7 in Switzerland." Epidemics (2021);
17
+ * doi: [10.1016/j.epidem.2021.100480](https://doi.org/10.1016/j.epidem.2021.100480)
18
+ *
19
+ * This component fetches aggregated data from LAPIS.
20
+ * Then the data is sent to `https://cov-spectrum.org/api/v2/computed/model/chen2021Fitness`
21
+ * which performs the logistic regression and calculates the relative growth advantage.
22
+ *
23
+ * ## Views
24
+ *
25
+ * ### Line View
26
+ *
27
+ * The line view shows the relative growth advantage over time in a line chart.
28
+ * The dots in the plot show the proportions of the focal variant (`numerator`) to the `denominator` variant
29
+ * for every day as observed in the data.
30
+ * The line shows a logistic curve fitted to the data points, including a 95% confidence interval.
31
+ */
7
32
  @customElement('gs-relative-growth-advantage')
8
33
  export class RelativeGrowthAdvantageComponent extends PreactLitAdapter {
34
+ /**
35
+ * The LAPIS filter for the focal variant.
36
+ */
9
37
  @property({ type: Object })
10
- numerator: LapisFilter = {};
38
+ numerator: Record<string, string | number | null | boolean> = {};
11
39
 
40
+ /**
41
+ * The LAPIS filter for the variant that the focal variant (`numerator`) should be compared to.
42
+ */
12
43
  @property({ type: Object })
13
- denominator: LapisFilter = {};
44
+ denominator: Record<string, string | number | null | boolean> = {};
14
45
 
46
+ /**
47
+ * The generation time represents the number of days over which the variant's relative growth advantage is measured.
48
+ * For example, if we set a generation time of 7 days, then we estimate the growth advantage per week.
49
+ */
15
50
  @property({ type: Number })
16
51
  generationTime: number = 7;
17
52
 
53
+ /**
54
+ * A list of tabs with views that this component should provide.
55
+ */
18
56
  @property({ type: Array })
19
- views: View[] = ['line'];
57
+ views: 'line'[] = ['line'];
58
+
59
+ /**
60
+ * The headline of the component. Set to an empty string to hide the headline.
61
+ */
62
+ @property({ type: String })
63
+ headline: string | undefined = 'Relative growth advantage';
64
+
65
+ /**
66
+ * The size of the component.
67
+ *
68
+ * If not set, the component will take the full width of its container with height 700px.
69
+ *
70
+ * The width and height should be a string with a unit in css style, e.g. '100%', '500px' or '50vh'.
71
+ * If the unit is %, the size will be relative to the container of the component.
72
+ */
73
+ @property({ type: Object })
74
+ size: { width?: string; height?: string } | undefined = undefined;
20
75
 
21
76
  override render() {
22
77
  return (
@@ -25,6 +80,8 @@ export class RelativeGrowthAdvantageComponent extends PreactLitAdapter {
25
80
  denominator={this.denominator}
26
81
  generationTime={this.generationTime}
27
82
  views={this.views}
83
+ size={this.size}
84
+ headline={this.headline}
28
85
  />
29
86
  );
30
87
  }
@@ -35,3 +92,9 @@ declare global {
35
92
  'gs-relative-growth-advantage': RelativeGrowthAdvantageComponent;
36
93
  }
37
94
  }
95
+
96
+ /* eslint-disable @typescript-eslint/no-unused-vars, no-unused-vars */
97
+ type NumeratorMatches = Expect<Equals<typeof RelativeGrowthAdvantageComponent.prototype.numerator, LapisFilter>>;
98
+ type DenominatorMatches = Expect<Equals<typeof RelativeGrowthAdvantageComponent.prototype.denominator, LapisFilter>>;
99
+ type ViewsMatches = Expect<Equals<typeof RelativeGrowthAdvantageComponent.prototype.views, View[]>>;
100
+ /* eslint-enable @typescript-eslint/no-unused-vars, no-unused-vars */
@@ -3,38 +3,84 @@ import { expect, waitFor } from '@storybook/test';
3
3
  import type { Meta, StoryObj } from '@storybook/web-components';
4
4
  import { html } from 'lit';
5
5
 
6
+ import { withComponentDocs } from '../../../.storybook/ComponentDocsBlock';
6
7
  import { LAPIS_URL } from '../../constants';
7
- import { type DateRangeSelectorProps } from '../../preact/dateRangeSelector/date-range-selector';
8
+ import {
9
+ type DateRangeSelectorProps,
10
+ PRESET_VALUE_ALL_TIMES,
11
+ PRESET_VALUE_CUSTOM,
12
+ PRESET_VALUE_LAST_2_MONTHS,
13
+ PRESET_VALUE_LAST_2_WEEKS,
14
+ PRESET_VALUE_LAST_3_MONTHS,
15
+ PRESET_VALUE_LAST_6_MONTHS,
16
+ PRESET_VALUE_LAST_MONTH,
17
+ } from '../../preact/dateRangeSelector/date-range-selector';
8
18
  import './date-range-selector-component';
9
19
  import '../app';
10
20
  import { toYYYYMMDD } from '../../preact/dateRangeSelector/dateConversion';
11
21
  import { withinShadowRoot } from '../withinShadowRoot.story';
12
22
 
13
- const meta: Meta<DateRangeSelectorProps> = {
23
+ const codeExample = String.raw`
24
+ <gs-date-range-selector
25
+ customSelectOptions='[{ "label": "Year 2021", "dateFrom": "2021-01-01", "dateTo": "2021-12-31" }]'
26
+ earliestDate="1970-01-01"
27
+ initialValue="${PRESET_VALUE_LAST_6_MONTHS}"
28
+ ></gs-date-range-selector>`;
29
+
30
+ const meta: Meta<DateRangeSelectorProps<'CustomDateRange'>> = {
14
31
  title: 'Input/DateRangeSelector',
15
32
  component: 'gs-date-range-selector',
16
- parameters: {
33
+ parameters: withComponentDocs({
17
34
  actions: {
18
35
  handles: ['gs-date-range-changed'],
19
36
  },
20
37
  fetchMock: {},
38
+ componentDocs: {
39
+ tag: 'gs-date-range-selector',
40
+ opensShadowDom: true,
41
+ expectsChildren: false,
42
+ codeExample,
43
+ },
44
+ }),
45
+ argTypes: {
46
+ initialValue: {
47
+ control: {
48
+ type: 'select',
49
+ },
50
+ options: [
51
+ PRESET_VALUE_CUSTOM,
52
+ PRESET_VALUE_ALL_TIMES,
53
+ PRESET_VALUE_LAST_2_WEEKS,
54
+ PRESET_VALUE_LAST_MONTH,
55
+ PRESET_VALUE_LAST_2_MONTHS,
56
+ PRESET_VALUE_LAST_3_MONTHS,
57
+ PRESET_VALUE_LAST_6_MONTHS,
58
+ 'CustomDateRange',
59
+ ],
60
+ },
61
+ },
62
+ args: {
63
+ customSelectOptions: [{ label: 'CustomDateRange', dateFrom: '2021-01-01', dateTo: '2021-12-31' }],
64
+ earliestDate: '1970-01-01',
65
+ initialValue: PRESET_VALUE_LAST_6_MONTHS,
21
66
  },
22
67
  decorators: [withActions],
68
+ tags: ['autodocs'],
23
69
  };
24
70
 
25
71
  export default meta;
26
72
 
27
- export const DateRangeSelectorStory: StoryObj<DateRangeSelectorProps> = {
73
+ export const DateRangeSelectorStory: StoryObj<DateRangeSelectorProps<'CustomDateRange'>> = {
28
74
  render: (args) =>
29
75
  html` <gs-app lapis="${LAPIS_URL}">
30
76
  <div class="max-w-screen-lg">
31
77
  <gs-date-range-selector
32
78
  .customSelectOptions=${args.customSelectOptions}
33
79
  .earliestDate=${args.earliestDate}
80
+ .initialValue=${args.initialValue}
34
81
  ></gs-date-range-selector>
35
82
  </div>
36
83
  </gs-app>`,
37
-
38
84
  play: async ({ canvasElement, step }) => {
39
85
  const canvas = await withinShadowRoot(canvasElement, 'gs-date-range-selector');
40
86
  const dateTo = () => canvas.getByPlaceholderText('Date to');
@@ -46,8 +92,4 @@ export const DateRangeSelectorStory: StoryObj<DateRangeSelectorProps> = {
46
92
  });
47
93
  });
48
94
  },
49
- args: {
50
- customSelectOptions: [{ label: 'CustomDateRange', dateFrom: '2021-01-01', dateTo: '2021-12-31' }],
51
- earliestDate: '1970-01-01',
52
- },
53
95
  };