@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/dashboard-components.js +229 -60
- package/dist/dashboard-components.js.map +1 -1
- package/dist/style.css +34 -1
- package/package.json +1 -1
- package/src/preact/aggregatedData/aggregate.tsx +12 -4
- package/src/preact/components/info.tsx +88 -1
- package/src/preact/mutations/mutations.tsx +32 -2
- package/src/preact/numberSequencesOverTime/number-sequences-over-time.tsx +40 -6
- package/src/preact/prevalenceOverTime/prevalence-over-time.stories.tsx +2 -1
- package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +44 -53
- package/src/web-components/visualization/gs-prevalence-over-time.tsx +2 -4
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.
|
|
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
|
@@ -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>
|
|
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, '"').replace(/'/g, ''')}
|
|
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
|
-
<
|
|
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
|
|
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 = ({
|
|
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 = ({
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
41
|
+
yAxisMaxLinear?: AxisMax;
|
|
42
|
+
yAxisMaxLogarithmic?: AxisMax;
|
|
45
43
|
}
|
|
46
44
|
|
|
47
|
-
export const PrevalenceOverTime: FunctionComponent<PrevalenceOverTimeProps> = (
|
|
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 {...
|
|
52
|
+
<PrevalenceOverTimeInner {...componentProps} />
|
|
54
53
|
</ResizeContainer>
|
|
55
54
|
</ErrorBoundary>
|
|
56
55
|
);
|
|
57
56
|
};
|
|
58
57
|
|
|
59
|
-
export const PrevalenceOverTimeInner: FunctionComponent<
|
|
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
|
-
|
|
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
|
-
|
|
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>
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
logarithmic: this.yAxisMaxLogarithmic,
|
|
181
|
-
}}
|
|
178
|
+
yAxisMaxLinear={this.yAxisMaxLinear}
|
|
179
|
+
yAxisMaxLogarithmic={this.yAxisMaxLogarithmic}
|
|
182
180
|
/>
|
|
183
181
|
);
|
|
184
182
|
}
|