@genspectrum/dashboard-components 0.6.10 → 0.6.12

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 (39) hide show
  1. package/dist/dashboard-components.js +1006 -618
  2. package/dist/dashboard-components.js.map +1 -1
  3. package/dist/genspectrum-components.d.ts +7 -7
  4. package/dist/style.css +147 -1
  5. package/package.json +2 -2
  6. package/src/preact/aggregatedData/aggregate.tsx +12 -4
  7. package/src/preact/components/checkbox-selector.stories.tsx +93 -11
  8. package/src/preact/components/checkbox-selector.tsx +19 -0
  9. package/src/preact/components/color-scale-selector-dropdown.tsx +5 -3
  10. package/src/preact/components/dropdown.tsx +3 -3
  11. package/src/preact/components/info.tsx +88 -1
  12. package/src/preact/components/mutation-type-selector.stories.tsx +115 -0
  13. package/src/preact/components/mutation-type-selector.tsx +33 -8
  14. package/src/preact/components/percent-input.stories.tsx +93 -0
  15. package/src/preact/components/percent-intput.tsx +4 -0
  16. package/src/preact/components/proportion-selector-dropdown.stories.tsx +2 -2
  17. package/src/preact/components/proportion-selector-dropdown.tsx +9 -7
  18. package/src/preact/components/proportion-selector.stories.tsx +4 -4
  19. package/src/preact/components/proportion-selector.tsx +46 -12
  20. package/src/preact/components/segment-selector.stories.tsx +151 -0
  21. package/src/preact/components/{SegmentSelector.tsx → segment-selector.tsx} +29 -20
  22. package/src/preact/mutationComparison/mutation-comparison.stories.tsx +1 -1
  23. package/src/preact/mutationComparison/mutation-comparison.tsx +1 -1
  24. package/src/preact/mutationComparison/queryMutationData.ts +1 -1
  25. package/src/preact/mutations/mutations-grid.tsx +5 -1
  26. package/src/preact/mutations/mutations.tsx +33 -3
  27. package/src/preact/mutations/queryMutations.ts +1 -1
  28. package/src/preact/mutationsOverTime/getFilteredMutationsOverTime.spec.ts +4 -4
  29. package/src/preact/mutationsOverTime/getFilteredMutationsOverTimeData.ts +3 -2
  30. package/src/preact/mutationsOverTime/mutations-over-time.tsx +1 -1
  31. package/src/preact/numberSequencesOverTime/number-sequences-over-time.tsx +43 -8
  32. package/src/preact/prevalenceOverTime/prevalence-over-time.stories.tsx +2 -1
  33. package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +44 -53
  34. package/src/preact/useQuery.ts +1 -1
  35. package/src/query/queryMutationsOverTime.ts +3 -3
  36. package/src/utils/map2d.spec.ts +83 -22
  37. package/src/utils/map2d.ts +158 -0
  38. package/src/web-components/visualization/gs-prevalence-over-time.tsx +2 -4
  39. package/src/utils/Map2d.ts +0 -75
@@ -1,5 +1,6 @@
1
1
  import { type Row } from 'gridjs';
2
2
  import { type FunctionComponent } from 'preact';
3
+ import { useMemo } from 'preact/hooks';
3
4
 
4
5
  import { getMutationsGridData } from './getMutationsGridData';
5
6
  import { type SequenceType, type SubstitutionOrDeletionEntry } from '../../types';
@@ -84,7 +85,10 @@ export const MutationsGrid: FunctionComponent<MutationsGridProps> = ({
84
85
  return {};
85
86
  };
86
87
 
87
- const tableData = getMutationsGridData(data, sequenceType, proportionInterval).map((row) => Object.values(row));
88
+ const tableData = useMemo(
89
+ () => getMutationsGridData(data, sequenceType, proportionInterval).map((row) => Object.values(row)),
90
+ [data, proportionInterval, sequenceType],
91
+ );
88
92
 
89
93
  return <Table data={tableData} columns={getHeaders()} pageSize={pageSize} />;
90
94
  };
@@ -14,18 +14,18 @@ import {
14
14
  type SubstitutionOrDeletionEntry,
15
15
  } from '../../types';
16
16
  import { LapisUrlContext } from '../LapisUrlContext';
17
- import { type DisplayedSegment, SegmentSelector, useDisplayedSegments } from '../components/SegmentSelector';
18
17
  import { CsvDownloadButton } from '../components/csv-download-button';
19
18
  import { ErrorBoundary } from '../components/error-boundary';
20
19
  import { ErrorDisplay } from '../components/error-display';
21
20
  import { Fullscreen } from '../components/fullscreen';
22
- import Info from '../components/info';
21
+ import Info, { InfoHeadline1, InfoHeadline2, InfoLink, InfoParagraph } from '../components/info';
23
22
  import { LoadingDisplay } from '../components/loading-display';
24
23
  import { type DisplayedMutationType, MutationTypeSelector } from '../components/mutation-type-selector';
25
24
  import { NoDataDisplay } from '../components/no-data-display';
26
25
  import type { ProportionInterval } from '../components/proportion-selector';
27
26
  import { ProportionSelectorDropdown } from '../components/proportion-selector-dropdown';
28
27
  import { ResizeContainer } from '../components/resize-container';
28
+ import { type DisplayedSegment, SegmentSelector, useDisplayedSegments } from '../components/segment-selector';
29
29
  import Tabs from '../components/tabs';
30
30
  import { useQuery } from '../useQuery';
31
31
 
@@ -208,8 +208,38 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
208
208
  filename='insertions.csv'
209
209
  />
210
210
  )}
211
- <Info>Info for mutations</Info>
211
+ <MutationsInfo />
212
212
  <Fullscreen />
213
213
  </>
214
214
  );
215
215
  };
216
+
217
+ const MutationsInfo = () => (
218
+ <Info>
219
+ <InfoHeadline1>Mutations</InfoHeadline1>
220
+ <InfoParagraph>
221
+ This shows mutations of a variant. There are three types of mutations:{' '}
222
+ <InfoLink href='https://www.genome.gov/genetics-glossary/Substitution'>substitutions</InfoLink>,{' '}
223
+ <InfoLink href='https://www.genome.gov/genetics-glossary/Deletion'>deletions</InfoLink> and{' '}
224
+ <InfoLink href='https://www.genome.gov/genetics-glossary/Insertion'>insertions</InfoLink>.
225
+ </InfoParagraph>
226
+ <InfoHeadline2>Proportion calculation</InfoHeadline2>
227
+ <InfoParagraph>
228
+ The proportion of a mutation is calculated by dividing the number of sequences with the mutation by the
229
+ total number of sequences with a non-ambiguous symbol at the position.
230
+ </InfoParagraph>
231
+ <InfoParagraph>
232
+ <b>Example:</b> Assume we look at nucleotide mutations at position 5 where the reference has a T and assume
233
+ there are 10 sequences in total:
234
+ <ul className='list-disc list-inside ml-2'>
235
+ <li>3 sequences have a C,</li>
236
+ <li>2 sequences have a T,</li>
237
+ <li>1 sequence has a G,</li>
238
+ <li>3 sequences have an N,</li>
239
+ <li>1 sequence has a Y (which means T or C),</li>
240
+ </ul>
241
+ then the proportion of the T5C mutation is 50%. The 4 sequences that have an N or Y are excluded from the
242
+ calculation.
243
+ </InfoParagraph>
244
+ </Info>
245
+ );
@@ -6,8 +6,8 @@ import {
6
6
  type MutationEntry,
7
7
  type SubstitutionOrDeletionEntry,
8
8
  } from '../../types';
9
- import { type DisplayedSegment } from '../components/SegmentSelector';
10
9
  import { type DisplayedMutationType } from '../components/mutation-type-selector';
10
+ import { type DisplayedSegment } from '../components/segment-selector';
11
11
 
12
12
  export async function queryMutationsData(
13
13
  lapisFilter: LapisFilter,
@@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest';
2
2
 
3
3
  import { filterDisplayedSegments, filterMutationTypes, filterProportion } from './getFilteredMutationsOverTimeData';
4
4
  import { type MutationOverTimeMutationValue } from '../../query/queryMutationsOverTime';
5
- import { Map2d } from '../../utils/Map2d';
5
+ import { Map2dBase } from '../../utils/map2d';
6
6
  import { Deletion, Substitution } from '../../utils/mutations';
7
7
  import { type Temporal } from '../../utils/temporal';
8
8
  import { yearMonthDay } from '../../utils/temporalTestHelpers';
@@ -10,7 +10,7 @@ import { yearMonthDay } from '../../utils/temporalTestHelpers';
10
10
  describe('getFilteredMutationOverTimeData', () => {
11
11
  describe('filterDisplayedSegments', () => {
12
12
  it('should filter by displayed segments', () => {
13
- const data = new Map2d<Substitution | Deletion, Temporal, MutationOverTimeMutationValue>();
13
+ const data = new Map2dBase<Substitution | Deletion, Temporal, MutationOverTimeMutationValue>();
14
14
 
15
15
  data.set(new Substitution('someSegment', 'A', 'T', 123), yearMonthDay('2021-01-01'), {
16
16
  count: 1,
@@ -36,7 +36,7 @@ describe('getFilteredMutationOverTimeData', () => {
36
36
 
37
37
  describe('filterMutationTypes', () => {
38
38
  it('should filter by mutation types', () => {
39
- const data = new Map2d<Substitution | Deletion, Temporal, MutationOverTimeMutationValue>();
39
+ const data = new Map2dBase<Substitution | Deletion, Temporal, MutationOverTimeMutationValue>();
40
40
 
41
41
  data.set(new Substitution('someSegment', 'A', 'T', 123), yearMonthDay('2021-01-01'), {
42
42
  count: 1,
@@ -120,7 +120,7 @@ describe('getFilteredMutationOverTimeData', () => {
120
120
  });
121
121
 
122
122
  function getMutationOverTimeData() {
123
- const data = new Map2d<Substitution | Deletion, Temporal, MutationOverTimeMutationValue>();
123
+ const data = new Map2dBase<Substitution | Deletion, Temporal, MutationOverTimeMutationValue>();
124
124
  data.set(someSubstitution, yearMonthDay('2021-01-01'), { count: 1, proportion: 0.1 });
125
125
  data.set(someSubstitution, yearMonthDay('2021-02-02'), { count: 99, proportion: 0.99 });
126
126
  return data;
@@ -1,8 +1,9 @@
1
1
  import { type Dataset } from '../../operator/Dataset';
2
2
  import { type MutationOverTimeDataGroupedByMutation } from '../../query/queryMutationsOverTime';
3
3
  import { type DeletionEntry, type SubstitutionEntry } from '../../types';
4
- import type { DisplayedSegment } from '../components/SegmentSelector';
4
+ import { Map2dView } from '../../utils/map2d';
5
5
  import type { DisplayedMutationType } from '../components/mutation-type-selector';
6
+ import type { DisplayedSegment } from '../components/segment-selector';
6
7
 
7
8
  export function getFilteredMutationOverTimeData(
8
9
  data: MutationOverTimeDataGroupedByMutation,
@@ -11,7 +12,7 @@ export function getFilteredMutationOverTimeData(
11
12
  displayedMutationTypes: DisplayedMutationType[],
12
13
  proportionInterval: { min: number; max: number },
13
14
  ) {
14
- const filteredData = data.copy();
15
+ const filteredData = new Map2dView(data);
15
16
  filterDisplayedSegments(displayedSegments, filteredData);
16
17
  filterMutationTypes(displayedMutationTypes, filteredData);
17
18
  filterProportion(filteredData, overallMutationData, proportionInterval);
@@ -18,7 +18,6 @@ import {
18
18
  } from '../../types';
19
19
  import { compareTemporal } from '../../utils/temporal';
20
20
  import { LapisUrlContext } from '../LapisUrlContext';
21
- import { type DisplayedSegment, SegmentSelector, useDisplayedSegments } from '../components/SegmentSelector';
22
21
  import { type ColorScale } from '../components/color-scale-selector';
23
22
  import { ColorScaleSelectorDropdown } from '../components/color-scale-selector-dropdown';
24
23
  import { CsvDownloadButton } from '../components/csv-download-button';
@@ -32,6 +31,7 @@ import { NoDataDisplay } from '../components/no-data-display';
32
31
  import type { ProportionInterval } from '../components/proportion-selector';
33
32
  import { ProportionSelectorDropdown } from '../components/proportion-selector-dropdown';
34
33
  import { ResizeContainer } from '../components/resize-container';
34
+ import { type DisplayedSegment, SegmentSelector, useDisplayedSegments } from '../components/segment-selector';
35
35
  import Tabs from '../components/tabs';
36
36
  import { sortSubstitutionsAndDeletions } from '../shared/sort/sortSubstitutionsAndDeletions';
37
37
  import { useQuery } from '../useQuery';
@@ -1,3 +1,4 @@
1
+ import { type FunctionComponent } from 'preact';
1
2
  import { useContext, useState } from 'preact/hooks';
2
3
 
3
4
  import { getNumberOfSequencesOverTimeTableData } from './getNumberOfSequencesOverTimeTableData';
@@ -61,8 +62,9 @@ const NumberSequencesOverTimeInner = ({
61
62
  }: NumberSequencesOverTimeInnerProps) => {
62
63
  const lapis = useContext(LapisUrlContext);
63
64
 
64
- const { data, error, isLoading } = useQuery(() =>
65
- queryNumberOfSequencesOverTime(lapis, lapisFilter, lapisDateField, granularity, smoothingWindow),
65
+ const { data, error, isLoading } = useQuery(
66
+ () => queryNumberOfSequencesOverTime(lapis, lapisFilter, lapisDateField, granularity, smoothingWindow),
67
+ [lapis, lapisFilter, lapisDateField, granularity, smoothingWindow],
66
68
  );
67
69
 
68
70
  if (isLoading) {
@@ -77,17 +79,32 @@ const NumberSequencesOverTimeInner = ({
77
79
  return <NoDataDisplay />;
78
80
  }
79
81
 
80
- return <NumberSequencesOverTimeTabs views={views} data={data} granularity={granularity} pageSize={pageSize} />;
82
+ return (
83
+ <NumberSequencesOverTimeTabs
84
+ views={views}
85
+ data={data}
86
+ granularity={granularity}
87
+ smoothingWindow={smoothingWindow}
88
+ pageSize={pageSize}
89
+ />
90
+ );
81
91
  };
82
92
 
83
93
  interface NumberSequencesOverTimeTabsProps {
84
94
  views: NumberSequencesOverTimeView[];
85
95
  data: NumberOfSequencesDatasets;
86
96
  granularity: TemporalGranularity;
97
+ smoothingWindow: number;
87
98
  pageSize: boolean | number;
88
99
  }
89
100
 
90
- const NumberSequencesOverTimeTabs = ({ views, data, granularity, pageSize }: NumberSequencesOverTimeTabsProps) => {
101
+ const NumberSequencesOverTimeTabs = ({
102
+ views,
103
+ data,
104
+ granularity,
105
+ smoothingWindow,
106
+ pageSize,
107
+ }: NumberSequencesOverTimeTabsProps) => {
91
108
  const [yAxisScaleType, setYAxisScaleType] = useState<ScaleType>('linear');
92
109
 
93
110
  const getTab = (view: NumberSequencesOverTimeView) => {
@@ -120,6 +137,7 @@ const NumberSequencesOverTimeTabs = ({ views, data, granularity, pageSize }: Num
120
137
  activeTab={activeTab}
121
138
  data={data}
122
139
  granularity={granularity}
140
+ smoothingWindow={smoothingWindow}
123
141
  yAxisScaleType={yAxisScaleType}
124
142
  setYAxisScaleType={setYAxisScaleType}
125
143
  />
@@ -134,9 +152,17 @@ interface ToolbarProps {
134
152
  granularity: TemporalGranularity;
135
153
  yAxisScaleType: ScaleType;
136
154
  setYAxisScaleType: (scaleType: ScaleType) => void;
155
+ smoothingWindow: number;
137
156
  }
138
157
 
139
- const Toolbar = ({ activeTab, data, granularity, yAxisScaleType, setYAxisScaleType }: ToolbarProps) => {
158
+ const Toolbar = ({
159
+ activeTab,
160
+ data,
161
+ granularity,
162
+ yAxisScaleType,
163
+ setYAxisScaleType,
164
+ smoothingWindow,
165
+ }: ToolbarProps) => {
140
166
  return (
141
167
  <>
142
168
  {activeTab !== 'Table' && (
@@ -151,17 +177,26 @@ const Toolbar = ({ activeTab, data, granularity, yAxisScaleType, setYAxisScaleTy
151
177
  getData={() => getNumberOfSequencesOverTimeTableData(data, granularity)}
152
178
  filename='number_of_sequences_over_time.csv'
153
179
  />
154
- <NumberSequencesOverTimeInfo />
180
+ <NumberSequencesOverTimeInfo granularity={granularity} smoothingWindow={smoothingWindow} />
155
181
  <Fullscreen />
156
182
  </>
157
183
  );
158
184
  };
159
185
 
160
- const NumberSequencesOverTimeInfo = () => (
186
+ type NumberSequencesOverTimeInfoProps = {
187
+ granularity: TemporalGranularity;
188
+ smoothingWindow: number;
189
+ };
190
+
191
+ const NumberSequencesOverTimeInfo: FunctionComponent<NumberSequencesOverTimeInfoProps> = ({
192
+ granularity,
193
+ smoothingWindow,
194
+ }) => (
161
195
  <Info>
162
196
  <InfoHeadline1>Number of sequences over time</InfoHeadline1>
163
197
  <InfoParagraph>
164
- <a href='https://github.com/GenSpectrum/dashboard-components/issues/315'>TODO</a>
198
+ This presents the number of available sequences of a variant per <b>{granularity}</b>
199
+ {smoothingWindow > 0 && `, smoothed using a ${smoothingWindow}-${granularity} sliding window`}.
165
200
  </InfoParagraph>
166
201
  </Info>
167
202
  );
@@ -51,7 +51,8 @@ const Template = {
51
51
  height={args.height}
52
52
  lapisDateField={args.lapisDateField}
53
53
  pageSize={args.pageSize}
54
- yAxisMaxConfig={args.yAxisMaxConfig}
54
+ yAxisMaxLinear={args.yAxisMaxLinear}
55
+ yAxisMaxLogarithmic={args.yAxisMaxLogarithmic}
55
56
  />
56
57
  </LapisUrlContext.Provider>
57
58
  ),
@@ -14,25 +14,22 @@ import { CsvDownloadButton } from '../components/csv-download-button';
14
14
  import { ErrorBoundary } from '../components/error-boundary';
15
15
  import { ErrorDisplay } from '../components/error-display';
16
16
  import { Fullscreen } from '../components/fullscreen';
17
- import Info, { InfoHeadline1, InfoParagraph } from '../components/info';
17
+ import Info, { InfoComponentCode, InfoHeadline1, InfoHeadline2, InfoParagraph } from '../components/info';
18
18
  import { LoadingDisplay } from '../components/loading-display';
19
19
  import { NoDataDisplay } from '../components/no-data-display';
20
20
  import { ResizeContainer } from '../components/resize-container';
21
21
  import { ScalingSelector } from '../components/scaling-selector';
22
22
  import Tabs from '../components/tabs';
23
23
  import { type ConfidenceIntervalMethod } from '../shared/charts/confideceInterval';
24
- import type { YAxisMaxConfig } from '../shared/charts/getYAxisMax';
24
+ import { type AxisMax } from '../shared/charts/getYAxisMax';
25
25
  import { type ScaleType } from '../shared/charts/getYAxisScale';
26
26
  import { useQuery } from '../useQuery';
27
27
 
28
28
  export type View = 'bar' | 'line' | 'bubble' | 'table';
29
29
 
30
- export interface PrevalenceOverTimeProps extends PrevalenceOverTimeInnerProps {
30
+ export interface PrevalenceOverTimeProps {
31
31
  width: string;
32
32
  height: string;
33
- }
34
-
35
- export interface PrevalenceOverTimeInnerProps {
36
33
  numeratorFilter: NamedLapisFilter | NamedLapisFilter[];
37
34
  denominatorFilter: LapisFilter;
38
35
  granularity: TemporalGranularity;
@@ -41,32 +38,25 @@ export interface PrevalenceOverTimeInnerProps {
41
38
  confidenceIntervalMethods: ConfidenceIntervalMethod[];
42
39
  lapisDateField: string;
43
40
  pageSize: boolean | number;
44
- yAxisMaxConfig: YAxisMaxConfig;
41
+ yAxisMaxLinear?: AxisMax;
42
+ yAxisMaxLogarithmic?: AxisMax;
45
43
  }
46
44
 
47
- export const PrevalenceOverTime: FunctionComponent<PrevalenceOverTimeProps> = ({ width, height, ...innerProps }) => {
45
+ export const PrevalenceOverTime: FunctionComponent<PrevalenceOverTimeProps> = (componentProps) => {
46
+ const { width, height } = componentProps;
48
47
  const size = { height, width };
49
48
 
50
49
  return (
51
50
  <ErrorBoundary size={size}>
52
51
  <ResizeContainer size={size}>
53
- <PrevalenceOverTimeInner {...innerProps} />
52
+ <PrevalenceOverTimeInner {...componentProps} />
54
53
  </ResizeContainer>
55
54
  </ErrorBoundary>
56
55
  );
57
56
  };
58
57
 
59
- export const PrevalenceOverTimeInner: FunctionComponent<PrevalenceOverTimeInnerProps> = ({
60
- numeratorFilter,
61
- denominatorFilter,
62
- granularity,
63
- smoothingWindow,
64
- views,
65
- confidenceIntervalMethods,
66
- lapisDateField,
67
- pageSize,
68
- yAxisMaxConfig,
69
- }) => {
58
+ export const PrevalenceOverTimeInner: FunctionComponent<PrevalenceOverTimeProps> = (componentProps) => {
59
+ const { numeratorFilter, denominatorFilter, granularity, smoothingWindow, lapisDateField } = componentProps;
70
60
  const lapis = useContext(LapisUrlContext);
71
61
 
72
62
  const { data, error, isLoading } = useQuery(
@@ -94,39 +84,21 @@ export const PrevalenceOverTimeInner: FunctionComponent<PrevalenceOverTimeInnerP
94
84
  return <NoDataDisplay />;
95
85
  }
96
86
 
97
- return (
98
- <PrevalenceOverTimeTabs
99
- views={views}
100
- data={data}
101
- granularity={granularity}
102
- confidenceIntervalMethods={confidenceIntervalMethods}
103
- pageSize={pageSize}
104
- yAxisMaxConfig={yAxisMaxConfig}
105
- />
106
- );
87
+ return <PrevalenceOverTimeTabs data={data} {...componentProps} />;
107
88
  };
108
89
 
109
- type PrevalenceOverTimeTabsProps = {
110
- views: View[];
90
+ type PrevalenceOverTimeTabsProps = PrevalenceOverTimeProps & {
111
91
  data: PrevalenceOverTimeData;
112
- granularity: TemporalGranularity;
113
- confidenceIntervalMethods: ConfidenceIntervalMethod[];
114
- pageSize: boolean | number;
115
- yAxisMaxConfig: YAxisMaxConfig;
116
92
  };
117
93
 
118
- const PrevalenceOverTimeTabs: FunctionComponent<PrevalenceOverTimeTabsProps> = ({
119
- views,
120
- data,
121
- granularity,
122
- confidenceIntervalMethods,
123
- pageSize,
124
- yAxisMaxConfig,
125
- }) => {
94
+ const PrevalenceOverTimeTabs: FunctionComponent<PrevalenceOverTimeTabsProps> = ({ data, ...componentProps }) => {
95
+ const { views, granularity, confidenceIntervalMethods, pageSize, yAxisMaxLinear, yAxisMaxLogarithmic } =
96
+ componentProps;
126
97
  const [yAxisScaleType, setYAxisScaleType] = useState<ScaleType>('linear');
127
98
  const [confidenceIntervalMethod, setConfidenceIntervalMethod] = useState<ConfidenceIntervalMethod>(
128
99
  confidenceIntervalMethods.length > 0 ? confidenceIntervalMethods[0] : 'none',
129
100
  );
101
+ const yAxisMaxConfig = { linear: yAxisMaxLinear, logarithmic: yAxisMaxLogarithmic };
130
102
 
131
103
  const getTab = (view: View) => {
132
104
  switch (view) {
@@ -181,20 +153,18 @@ const PrevalenceOverTimeTabs: FunctionComponent<PrevalenceOverTimeTabsProps> = (
181
153
  yAxisScaleType={yAxisScaleType}
182
154
  setYAxisScaleType={setYAxisScaleType}
183
155
  data={data}
184
- granularity={granularity}
185
- confidenceIntervalMethods={confidenceIntervalMethods}
186
156
  confidenceIntervalMethod={confidenceIntervalMethod}
187
157
  setConfidenceIntervalMethod={setConfidenceIntervalMethod}
158
+ {...componentProps}
188
159
  />
189
160
  );
190
161
 
191
162
  return <Tabs tabs={tabs} toolbar={toolbar} />;
192
163
  };
193
164
 
194
- type ToolbarProps = {
165
+ type ToolbarProps = PrevalenceOverTimeProps & {
195
166
  activeTab: string;
196
167
  data: PrevalenceOverTimeData;
197
- granularity: TemporalGranularity;
198
168
  yAxisScaleType: ScaleType;
199
169
  setYAxisScaleType: (scaleType: ScaleType) => void;
200
170
  confidenceIntervalMethods: ConfidenceIntervalMethod[];
@@ -206,12 +176,12 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
206
176
  activeTab,
207
177
  yAxisScaleType,
208
178
  setYAxisScaleType,
209
- confidenceIntervalMethods,
210
179
  confidenceIntervalMethod,
211
180
  setConfidenceIntervalMethod,
212
181
  data,
213
- granularity,
182
+ ...componentProps
214
183
  }) => {
184
+ const { confidenceIntervalMethods, granularity } = componentProps;
215
185
  return (
216
186
  <>
217
187
  {activeTab !== 'Table' && (
@@ -230,17 +200,38 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
230
200
  filename='prevalence_over_time.csv'
231
201
  />
232
202
 
233
- <PrevalenceOverTimeInfo />
203
+ <PrevalenceOverTimeInfo {...componentProps} />
234
204
  <Fullscreen />
235
205
  </>
236
206
  );
237
207
  };
238
208
 
239
- const PrevalenceOverTimeInfo: FunctionComponent = () => {
209
+ const PrevalenceOverTimeInfo: FunctionComponent<PrevalenceOverTimeProps> = (componentProps) => {
210
+ const { granularity, smoothingWindow, views } = componentProps;
211
+ const lapis = useContext(LapisUrlContext);
240
212
  return (
241
213
  <Info>
242
214
  <InfoHeadline1>Prevalence over time</InfoHeadline1>
243
- <InfoParagraph>Prevalence over time info.</InfoParagraph>
215
+ <InfoParagraph>
216
+ This presents the proportion of one or more variants per <b>{granularity}</b>
217
+ {smoothingWindow > 0 && `, smoothed using a ${smoothingWindow}-${granularity} sliding window`}. The
218
+ proportion is calculated as the number of sequences of the variant(s) divided by the number of sequences
219
+ that match the <code>denominatorFilter</code> (see below).
220
+ </InfoParagraph>
221
+ <InfoParagraph>
222
+ Sequences that have no assigned date will not be shown in the line and bubble chart. They will show up
223
+ in the bar and table view with date "unknown".
224
+ </InfoParagraph>
225
+ {views.includes('bubble') && (
226
+ <>
227
+ <InfoHeadline2>Bubble chart</InfoHeadline2>
228
+ <InfoParagraph>
229
+ The size of the bubble scales with the total number of available sequences from the{' '}
230
+ {granularity}.
231
+ </InfoParagraph>
232
+ </>
233
+ )}
234
+ <InfoComponentCode componentName='prevalence-over-time' params={componentProps} lapisUrl={lapis} />
244
235
  </Info>
245
236
  );
246
237
  };
@@ -1,6 +1,6 @@
1
1
  import { useEffect, useState } from 'preact/hooks';
2
2
 
3
- export function useQuery<Data>(fetchDataCallback: () => Promise<Data>, dependencies: unknown[] = []) {
3
+ export function useQuery<Data>(fetchDataCallback: () => Promise<Data>, dependencies: unknown[]) {
4
4
  const [data, setData] = useState<Data | null>(null);
5
5
  const [error, setError] = useState<Error | null>(null);
6
6
  const [isLoading, setIsLoading] = useState(true);
@@ -12,7 +12,7 @@ import {
12
12
  type SubstitutionOrDeletionEntry,
13
13
  type TemporalGranularity,
14
14
  } from '../types';
15
- import { Map2d } from '../utils/Map2d';
15
+ import { Map2dBase, type Map2d } from '../utils/map2d';
16
16
  import { type Deletion, type Substitution } from '../utils/mutations';
17
17
  import {
18
18
  dateRangeCompare,
@@ -154,7 +154,7 @@ function fetchAndPrepareSubstitutionsOrDeletions(filter: LapisFilter, sequenceTy
154
154
  }
155
155
 
156
156
  export function groupByMutation(data: MutationOverTimeData[]) {
157
- const dataArray = new Map2d<Substitution | Deletion, Temporal, MutationOverTimeMutationValue>(
157
+ const dataArray = new Map2dBase<Substitution | Deletion, Temporal, MutationOverTimeMutationValue>(
158
158
  (mutation) => mutation.code,
159
159
  (date) => date.toString(),
160
160
  );
@@ -174,7 +174,7 @@ export function groupByMutation(data: MutationOverTimeData[]) {
174
174
  }
175
175
 
176
176
  function addZeroValuesForDatesWithNoMutationData(
177
- dataArray: Map2d<Substitution | Deletion, Temporal, MutationOverTimeMutationValue>,
177
+ dataArray: Map2dBase<Substitution | Deletion, Temporal, MutationOverTimeMutationValue>,
178
178
  data: MutationOverTimeData[],
179
179
  ) {
180
180
  if (dataArray.getFirstAxisKeys().length !== 0) {