@genspectrum/dashboard-components 0.6.10 → 0.6.11

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/dist/style.css CHANGED
@@ -376,7 +376,7 @@ input[type="range"] {
376
376
  background-color: #C6C6C6;
377
377
  pointer-events: none;
378
378
  }/*
379
- ! tailwindcss v3.4.6 | MIT License | https://tailwindcss.com
379
+ ! tailwindcss v3.4.7 | MIT License | https://tailwindcss.com
380
380
  *//*
381
381
  1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
382
382
  2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
@@ -1362,6 +1362,10 @@ html {
1362
1362
  border-radius: inherit;
1363
1363
  }
1364
1364
  }
1365
+ .link {
1366
+ cursor: pointer;
1367
+ text-decoration-line: underline;
1368
+ }
1365
1369
  .menu li.disabled {
1366
1370
  cursor: not-allowed;
1367
1371
  -webkit-user-select: none;
@@ -1858,6 +1862,14 @@ input.tab:checked + .tab-content,
1858
1862
  .join > :where(*:not(:first-child)):is(.btn) {
1859
1863
  margin-inline-start: calc(var(--border-btn) * -1);
1860
1864
  }
1865
+ .link:focus {
1866
+ outline: 2px solid transparent;
1867
+ outline-offset: 2px;
1868
+ }
1869
+ .link:focus-visible {
1870
+ outline: 2px solid currentColor;
1871
+ outline-offset: 2px;
1872
+ }
1861
1873
  .loading {
1862
1874
  pointer-events: none;
1863
1875
  display: inline-block;
@@ -2884,6 +2896,9 @@ input.tab:checked + .tab-content,
2884
2896
  .ml-1 {
2885
2897
  margin-left: 0.25rem;
2886
2898
  }
2899
+ .ml-2 {
2900
+ margin-left: 0.5rem;
2901
+ }
2887
2902
  .ml-2\.5 {
2888
2903
  margin-left: 0.625rem;
2889
2904
  }
@@ -2977,6 +2992,12 @@ input.tab:checked + .tab-content,
2977
2992
  .resize {
2978
2993
  resize: both;
2979
2994
  }
2995
+ .list-inside {
2996
+ list-style-position: inside;
2997
+ }
2998
+ .list-disc {
2999
+ list-style-type: disc;
3000
+ }
2980
3001
  .flex-row {
2981
3002
  flex-direction: row;
2982
3003
  }
@@ -3007,6 +3028,9 @@ input.tab:checked + .tab-content,
3007
3028
  .overflow-auto {
3008
3029
  overflow: auto;
3009
3030
  }
3031
+ .overflow-x-auto {
3032
+ overflow-x: auto;
3033
+ }
3010
3034
  .whitespace-nowrap {
3011
3035
  white-space: nowrap;
3012
3036
  }
@@ -3016,6 +3040,9 @@ input.tab:checked + .tab-content,
3016
3040
  .rounded-full {
3017
3041
  border-radius: 9999px;
3018
3042
  }
3043
+ .rounded-lg {
3044
+ border-radius: 0.5rem;
3045
+ }
3019
3046
  .rounded-md {
3020
3047
  border-radius: 0.375rem;
3021
3048
  }
@@ -3271,6 +3298,12 @@ input.tab:checked + .tab-content,
3271
3298
  }
3272
3299
  .peer:hover ~ .peer-hover\:visible {
3273
3300
  visibility: visible;
3301
+ }
3302
+ @media (min-width: 640px) {
3303
+
3304
+ .sm\:max-w-5xl {
3305
+ max-width: 64rem;
3306
+ }
3274
3307
  }.flatpickr-calendar{background:transparent;opacity:0;display:none;text-align:center;visibility:hidden;padding:0;-webkit-animation:none;animation:none;direction:ltr;border:0;font-size:14px;line-height:24px;border-radius:5px;position:absolute;width:307.875px;-webkit-box-sizing:border-box;box-sizing:border-box;-ms-touch-action:manipulation;touch-action:manipulation;background:#fff;-webkit-box-shadow:1px 0 0 #e6e6e6,-1px 0 0 #e6e6e6,0 1px 0 #e6e6e6,0 -1px 0 #e6e6e6,0 3px 13px rgba(0,0,0,0.08);box-shadow:1px 0 0 #e6e6e6,-1px 0 0 #e6e6e6,0 1px 0 #e6e6e6,0 -1px 0 #e6e6e6,0 3px 13px rgba(0,0,0,0.08)}.flatpickr-calendar.open,.flatpickr-calendar.inline{opacity:1;max-height:640px;visibility:visible}.flatpickr-calendar.open{display:inline-block;z-index:99999}.flatpickr-calendar.animate.open{-webkit-animation:fpFadeInDown 300ms cubic-bezier(.23,1,.32,1);animation:fpFadeInDown 300ms cubic-bezier(.23,1,.32,1)}.flatpickr-calendar.inline{display:block;position:relative;top:2px}.flatpickr-calendar.static{position:absolute;top:calc(100% + 2px)}.flatpickr-calendar.static.open{z-index:999;display:block}.flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n+1) .flatpickr-day.inRange:nth-child(7n+7){-webkit-box-shadow:none !important;box-shadow:none !important}.flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n+2) .flatpickr-day.inRange:nth-child(7n+1){-webkit-box-shadow:-2px 0 0 #e6e6e6,5px 0 0 #e6e6e6;box-shadow:-2px 0 0 #e6e6e6,5px 0 0 #e6e6e6}.flatpickr-calendar .hasWeeks .dayContainer,.flatpickr-calendar .hasTime .dayContainer{border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.flatpickr-calendar .hasWeeks .dayContainer{border-left:0}.flatpickr-calendar.hasTime .flatpickr-time{height:40px;border-top:1px solid #e6e6e6}.flatpickr-calendar.noCalendar.hasTime .flatpickr-time{height:auto}.flatpickr-calendar:before,.flatpickr-calendar:after{position:absolute;display:block;pointer-events:none;border:solid transparent;content:'';height:0;width:0;left:22px}.flatpickr-calendar.rightMost:before,.flatpickr-calendar.arrowRight:before,.flatpickr-calendar.rightMost:after,.flatpickr-calendar.arrowRight:after{left:auto;right:22px}.flatpickr-calendar.arrowCenter:before,.flatpickr-calendar.arrowCenter:after{left:50%;right:50%}.flatpickr-calendar:before{border-width:5px;margin:0 -5px}.flatpickr-calendar:after{border-width:4px;margin:0 -4px}.flatpickr-calendar.arrowTop:before,.flatpickr-calendar.arrowTop:after{bottom:100%}.flatpickr-calendar.arrowTop:before{border-bottom-color:#e6e6e6}.flatpickr-calendar.arrowTop:after{border-bottom-color:#fff}.flatpickr-calendar.arrowBottom:before,.flatpickr-calendar.arrowBottom:after{top:100%}.flatpickr-calendar.arrowBottom:before{border-top-color:#e6e6e6}.flatpickr-calendar.arrowBottom:after{border-top-color:#fff}.flatpickr-calendar:focus{outline:0}.flatpickr-wrapper{position:relative;display:inline-block}.flatpickr-months{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.flatpickr-months .flatpickr-month{background:transparent;color:rgba(0,0,0,0.9);fill:rgba(0,0,0,0.9);height:34px;line-height:1;text-align:center;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;overflow:hidden;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}.flatpickr-months .flatpickr-prev-month,.flatpickr-months .flatpickr-next-month{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;text-decoration:none;cursor:pointer;position:absolute;top:0;height:34px;padding:10px;z-index:3;color:rgba(0,0,0,0.9);fill:rgba(0,0,0,0.9)}.flatpickr-months .flatpickr-prev-month.flatpickr-disabled,.flatpickr-months .flatpickr-next-month.flatpickr-disabled{display:none}.flatpickr-months .flatpickr-prev-month i,.flatpickr-months .flatpickr-next-month i{position:relative}.flatpickr-months .flatpickr-prev-month.flatpickr-prev-month,.flatpickr-months .flatpickr-next-month.flatpickr-prev-month{/*
3275
3308
  /*rtl:begin:ignore*/left:0/*
3276
3309
  /*rtl:end:ignore*/}/*
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genspectrum/dashboard-components",
3
- "version": "0.6.10",
3
+ "version": "0.6.11",
4
4
  "description": "GenSpectrum web components for building dashboards",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-only",
@@ -9,7 +9,7 @@ import { CsvDownloadButton } from '../components/csv-download-button';
9
9
  import { ErrorBoundary } from '../components/error-boundary';
10
10
  import { ErrorDisplay } from '../components/error-display';
11
11
  import { Fullscreen } from '../components/fullscreen';
12
- import Info from '../components/info';
12
+ import Info, { InfoHeadline1, InfoParagraph } from '../components/info';
13
13
  import { LoadingDisplay } from '../components/loading-display';
14
14
  import { NoDataDisplay } from '../components/no-data-display';
15
15
  import { ResizeContainer } from '../components/resize-container';
@@ -94,18 +94,26 @@ const AggregatedDataTabs: FunctionComponent<AggregatedDataTabsProps> = ({ data,
94
94
 
95
95
  const tabs = views.map((view) => getTab(view));
96
96
 
97
- return <Tabs tabs={tabs} toolbar={<Toolbar data={data} />} />;
97
+ return <Tabs tabs={tabs} toolbar={<Toolbar data={data} fields={fields} />} />;
98
98
  };
99
99
 
100
100
  type ToolbarProps = {
101
101
  data: AggregateData;
102
+ fields: string[];
102
103
  };
103
104
 
104
- const Toolbar: FunctionComponent<ToolbarProps> = ({ data }) => {
105
+ const Toolbar: FunctionComponent<ToolbarProps> = ({ data, fields }) => {
105
106
  return (
106
107
  <div class='flex flex-row'>
107
108
  <CsvDownloadButton className='mx-1 btn btn-xs' getData={() => data} filename='aggregate.csv' />
108
- <Info>Info for aggregate</Info>
109
+ <Info>
110
+ <InfoHeadline1>Aggregated data</InfoHeadline1>
111
+ <InfoParagraph>
112
+ This table shows the number and proportion of sequences stratified by the following fields:{' '}
113
+ {fields.join(', ')}. The proportion is calculated with respect to the total count within the
114
+ filtered dataset.
115
+ </InfoParagraph>
116
+ </Info>
109
117
  <Fullscreen />
110
118
  </div>
111
119
  );
@@ -16,7 +16,7 @@ const Info: FunctionComponent<InfoProps> = ({ children }) => {
16
16
  ?
17
17
  </button>
18
18
  <dialog ref={dialogRef} className={'modal modal-bottom sm:modal-middle'}>
19
- <div className='modal-box'>
19
+ <div className='modal-box sm:max-w-5xl'>
20
20
  <form method='dialog'>
21
21
  <button className='btn btn-sm btn-circle btn-ghost absolute right-2 top-2'>✕</button>
22
22
  </form>
@@ -55,4 +55,91 @@ export const InfoLink: FunctionComponent<{ href: string }> = ({ children, href }
55
55
  );
56
56
  };
57
57
 
58
+ export type InfoComponentCodeProps = {
59
+ componentName: string;
60
+ params: object;
61
+ lapisUrl: string;
62
+ };
63
+
64
+ export const InfoComponentCode: FunctionComponent<InfoComponentCodeProps> = ({ componentName, params, lapisUrl }) => {
65
+ const componentCode = componentParametersToCode(componentName, params, lapisUrl);
66
+ const codePenData = {
67
+ title: 'GenSpectrum dashboard component',
68
+ html: generateFullExampleCode(componentCode, componentName),
69
+ layout: 'left',
70
+ editors: '100',
71
+ };
72
+ return (
73
+ <>
74
+ <InfoHeadline2>Use this component yourself</InfoHeadline2>
75
+ <InfoParagraph>
76
+ This component was created using the following parameters:
77
+ <div className='p-4 border border-gray-200 rounded-lg overflow-x-auto'>
78
+ <pre>
79
+ <code>{componentCode}</code>
80
+ </pre>
81
+ </div>
82
+ </InfoParagraph>
83
+ <InfoParagraph>
84
+ You can add this component to your own website using the{' '}
85
+ <InfoLink href='https://github.com/GenSpectrum/dashboard-components'>
86
+ GenSpectrum dashboard components library
87
+ </InfoLink>{' '}
88
+ and the code from above.
89
+ </InfoParagraph>
90
+ <InfoParagraph>
91
+ <form action='https://codepen.io/pen/define' method='POST' target='_blank'>
92
+ <input
93
+ type='hidden'
94
+ name='data'
95
+ value={JSON.stringify(codePenData).replace(/"/g, '&quot;').replace(/'/g, '&apos;')}
96
+ />
97
+
98
+ <button className='text-blue-600 hover:text-blue-800' type='submit'>
99
+ Click here to try it out on CodePen.
100
+ </button>
101
+ </form>
102
+ </InfoParagraph>
103
+ </>
104
+ );
105
+ };
106
+
58
107
  export default Info;
108
+
109
+ function componentParametersToCode(componentName: string, params: object, lapisUrl: string) {
110
+ const stringifyIfNeeded = (value: unknown) => {
111
+ return typeof value === 'object' ? JSON.stringify(value) : value;
112
+ };
113
+
114
+ const attributes = indentLines(
115
+ Object.entries(params)
116
+ .map(([key, value]) => `${key}='${stringifyIfNeeded(value)}'`)
117
+ .join('\n'),
118
+ 4,
119
+ );
120
+ return `<gs-app lapis="${lapisUrl}">\n <gs-${componentName}\n${attributes}\n />\n</gs-app>`;
121
+ }
122
+
123
+ function generateFullExampleCode(componentCode: string, componentName: string) {
124
+ const storyBookPath = `/docs/visualization-${componentName}--docs`;
125
+ return `<html>
126
+ <head>
127
+ <script type="module" src="https://unpkg.com/@genspectrum/dashboard-components@latest/dist/dashboard-components.js"></script>
128
+ <link rel="stylesheet" href="https://unpkg.com/@genspectrum/dashboard-components@latest/dist/style.css" />
129
+ </head>
130
+
131
+ <body>
132
+ <!-- Component documentation: https://genspectrum.github.io/dashboard-components/?path=${storyBookPath} -->
133
+ ${indentLines(componentCode, 2)}
134
+ </body>
135
+ </html>
136
+ `;
137
+ }
138
+
139
+ function indentLines(text: string, numberSpaces: number) {
140
+ const spaces = ' '.repeat(numberSpaces);
141
+ return text
142
+ .split('\n')
143
+ .map((line) => spaces + line)
144
+ .join('\n');
145
+ }
@@ -19,7 +19,7 @@ import { CsvDownloadButton } from '../components/csv-download-button';
19
19
  import { ErrorBoundary } from '../components/error-boundary';
20
20
  import { ErrorDisplay } from '../components/error-display';
21
21
  import { Fullscreen } from '../components/fullscreen';
22
- import Info from '../components/info';
22
+ import Info, { InfoHeadline1, InfoHeadline2, InfoLink, InfoParagraph } from '../components/info';
23
23
  import { LoadingDisplay } from '../components/loading-display';
24
24
  import { type DisplayedMutationType, MutationTypeSelector } from '../components/mutation-type-selector';
25
25
  import { NoDataDisplay } from '../components/no-data-display';
@@ -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
+ );
@@ -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';
@@ -77,17 +78,32 @@ const NumberSequencesOverTimeInner = ({
77
78
  return <NoDataDisplay />;
78
79
  }
79
80
 
80
- return <NumberSequencesOverTimeTabs views={views} data={data} granularity={granularity} pageSize={pageSize} />;
81
+ return (
82
+ <NumberSequencesOverTimeTabs
83
+ views={views}
84
+ data={data}
85
+ granularity={granularity}
86
+ smoothingWindow={smoothingWindow}
87
+ pageSize={pageSize}
88
+ />
89
+ );
81
90
  };
82
91
 
83
92
  interface NumberSequencesOverTimeTabsProps {
84
93
  views: NumberSequencesOverTimeView[];
85
94
  data: NumberOfSequencesDatasets;
86
95
  granularity: TemporalGranularity;
96
+ smoothingWindow: number;
87
97
  pageSize: boolean | number;
88
98
  }
89
99
 
90
- const NumberSequencesOverTimeTabs = ({ views, data, granularity, pageSize }: NumberSequencesOverTimeTabsProps) => {
100
+ const NumberSequencesOverTimeTabs = ({
101
+ views,
102
+ data,
103
+ granularity,
104
+ smoothingWindow,
105
+ pageSize,
106
+ }: NumberSequencesOverTimeTabsProps) => {
91
107
  const [yAxisScaleType, setYAxisScaleType] = useState<ScaleType>('linear');
92
108
 
93
109
  const getTab = (view: NumberSequencesOverTimeView) => {
@@ -120,6 +136,7 @@ const NumberSequencesOverTimeTabs = ({ views, data, granularity, pageSize }: Num
120
136
  activeTab={activeTab}
121
137
  data={data}
122
138
  granularity={granularity}
139
+ smoothingWindow={smoothingWindow}
123
140
  yAxisScaleType={yAxisScaleType}
124
141
  setYAxisScaleType={setYAxisScaleType}
125
142
  />
@@ -134,9 +151,17 @@ interface ToolbarProps {
134
151
  granularity: TemporalGranularity;
135
152
  yAxisScaleType: ScaleType;
136
153
  setYAxisScaleType: (scaleType: ScaleType) => void;
154
+ smoothingWindow: number;
137
155
  }
138
156
 
139
- const Toolbar = ({ activeTab, data, granularity, yAxisScaleType, setYAxisScaleType }: ToolbarProps) => {
157
+ const Toolbar = ({
158
+ activeTab,
159
+ data,
160
+ granularity,
161
+ yAxisScaleType,
162
+ setYAxisScaleType,
163
+ smoothingWindow,
164
+ }: ToolbarProps) => {
140
165
  return (
141
166
  <>
142
167
  {activeTab !== 'Table' && (
@@ -151,17 +176,26 @@ const Toolbar = ({ activeTab, data, granularity, yAxisScaleType, setYAxisScaleTy
151
176
  getData={() => getNumberOfSequencesOverTimeTableData(data, granularity)}
152
177
  filename='number_of_sequences_over_time.csv'
153
178
  />
154
- <NumberSequencesOverTimeInfo />
179
+ <NumberSequencesOverTimeInfo granularity={granularity} smoothingWindow={smoothingWindow} />
155
180
  <Fullscreen />
156
181
  </>
157
182
  );
158
183
  };
159
184
 
160
- const NumberSequencesOverTimeInfo = () => (
185
+ type NumberSequencesOverTimeInfoProps = {
186
+ granularity: TemporalGranularity;
187
+ smoothingWindow: number;
188
+ };
189
+
190
+ const NumberSequencesOverTimeInfo: FunctionComponent<NumberSequencesOverTimeInfoProps> = ({
191
+ granularity,
192
+ smoothingWindow,
193
+ }) => (
161
194
  <Info>
162
195
  <InfoHeadline1>Number of sequences over time</InfoHeadline1>
163
196
  <InfoParagraph>
164
- <a href='https://github.com/GenSpectrum/dashboard-components/issues/315'>TODO</a>
197
+ This presents the number of available sequences of a variant per <b>{granularity}</b>
198
+ {smoothingWindow > 0 && `, smoothed using a ${smoothingWindow}-${granularity} sliding window`}.
165
199
  </InfoParagraph>
166
200
  </Info>
167
201
  );
@@ -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
  };
@@ -175,10 +175,8 @@ export class PrevalenceOverTimeComponent extends PreactLitAdapterWithGridJsStyle
175
175
  height={this.height}
176
176
  lapisDateField={this.lapisDateField}
177
177
  pageSize={this.pageSize}
178
- yAxisMaxConfig={{
179
- linear: this.yAxisMaxLinear,
180
- logarithmic: this.yAxisMaxLogarithmic,
181
- }}
178
+ yAxisMaxLinear={this.yAxisMaxLinear}
179
+ yAxisMaxLogarithmic={this.yAxisMaxLogarithmic}
182
180
  />
183
181
  );
184
182
  }