@genspectrum/dashboard-components 0.6.6 → 0.6.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/custom-elements.json +16 -0
- package/dist/dashboard-components.js +223 -142
- package/dist/dashboard-components.js.map +1 -1
- package/dist/genspectrum-components.d.ts +3 -3
- package/dist/style.css +42 -6
- package/package.json +1 -1
- package/src/preact/aggregatedData/aggregate.tsx +1 -1
- package/src/preact/components/color-scale-selector-dropdown.stories.tsx +27 -0
- package/src/preact/components/color-scale-selector-dropdown.tsx +17 -0
- package/src/preact/components/color-scale-selector.stories.tsx +61 -0
- package/src/preact/components/color-scale-selector.tsx +79 -0
- package/src/preact/components/info.stories.tsx +37 -10
- package/src/preact/components/info.tsx +22 -43
- package/src/preact/components/min-max-range-slider.tsx +1 -1
- package/src/preact/components/tooltip.tsx +2 -2
- package/src/preact/mutationComparison/mutation-comparison.tsx +1 -1
- package/src/preact/mutationFilter/mutation-filter-info.tsx +1 -1
- package/src/preact/mutationFilter/mutation-filter.stories.tsx +1 -1
- package/src/preact/mutationFilter/mutation-filter.tsx +2 -3
- package/src/preact/mutations/mutations.tsx +1 -1
- package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +59 -34
- package/src/preact/mutationsOverTime/mutations-over-time.tsx +18 -3
- package/src/preact/numberSequencesOverTime/number-sequences-over-time.tsx +1 -1
- package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +1 -1
- package/src/preact/shared/charts/colors.ts +1 -1
- package/src/web-components/visualization/gs-mutations-over-time.stories.ts +8 -0
- package/src/preact/shared/icons/DeleteIcon.tsx +0 -17
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { Fragment, type FunctionComponent } from 'preact';
|
|
1
|
+
import { Fragment, type FunctionComponent, type RefObject } from 'preact';
|
|
2
|
+
import { useEffect, useRef, useState } from 'preact/hooks';
|
|
2
3
|
|
|
3
4
|
import {
|
|
4
5
|
type MutationOverTimeDataGroupedByMutation,
|
|
@@ -6,22 +7,28 @@ import {
|
|
|
6
7
|
} from '../../query/queryMutationsOverTime';
|
|
7
8
|
import { type Deletion, type Substitution } from '../../utils/mutations';
|
|
8
9
|
import { compareTemporal, type Temporal, YearMonthDay } from '../../utils/temporal';
|
|
10
|
+
import { type ColorScale, getColorWithingScale, getTextColorForScale } from '../components/color-scale-selector';
|
|
9
11
|
import Tooltip, { type TooltipPosition } from '../components/tooltip';
|
|
10
|
-
import { singleGraphColorRGBByName } from '../shared/charts/colors';
|
|
11
12
|
import { formatProportion } from '../shared/table/formatProportion';
|
|
12
13
|
|
|
13
14
|
export interface MutationsOverTimeGridProps {
|
|
14
15
|
data: MutationOverTimeDataGroupedByMutation;
|
|
16
|
+
colorScale: ColorScale;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
const MAX_NUMBER_OF_GRID_ROWS = 100;
|
|
20
|
+
const MUTATION_CELL_WIDTH_REM = 8;
|
|
18
21
|
|
|
19
|
-
const MutationsOverTimeGrid: FunctionComponent<MutationsOverTimeGridProps> = ({ data }) => {
|
|
22
|
+
const MutationsOverTimeGrid: FunctionComponent<MutationsOverTimeGridProps> = ({ data, colorScale }) => {
|
|
20
23
|
const allMutations = data.getFirstAxisKeys();
|
|
21
24
|
const shownMutations = allMutations.slice(0, MAX_NUMBER_OF_GRID_ROWS);
|
|
22
25
|
|
|
23
26
|
const dates = data.getSecondAxisKeys().sort((a, b) => compareTemporal(a, b));
|
|
24
27
|
|
|
28
|
+
const [showProportionText, setShowProportionText] = useState(false);
|
|
29
|
+
const gridRef = useRef<HTMLDivElement>(null);
|
|
30
|
+
useShowProportion(gridRef, dates.length, setShowProportionText);
|
|
31
|
+
|
|
25
32
|
return (
|
|
26
33
|
<>
|
|
27
34
|
{allMutations.length > MAX_NUMBER_OF_GRID_ROWS && (
|
|
@@ -31,10 +38,11 @@ const MutationsOverTimeGrid: FunctionComponent<MutationsOverTimeGridProps> = ({
|
|
|
31
38
|
</div>
|
|
32
39
|
)}
|
|
33
40
|
<div
|
|
41
|
+
ref={gridRef}
|
|
34
42
|
style={{
|
|
35
43
|
display: 'grid',
|
|
36
44
|
gridTemplateRows: `repeat(${shownMutations.length}, 24px)`,
|
|
37
|
-
gridTemplateColumns:
|
|
45
|
+
gridTemplateColumns: `${MUTATION_CELL_WIDTH_REM}rem repeat(${dates.length}, minmax(0.05rem, 1fr))`,
|
|
38
46
|
}}
|
|
39
47
|
>
|
|
40
48
|
{shownMutations.map((mutation, rowIndex) => {
|
|
@@ -64,6 +72,8 @@ const MutationsOverTimeGrid: FunctionComponent<MutationsOverTimeGridProps> = ({
|
|
|
64
72
|
date={date}
|
|
65
73
|
mutation={mutation}
|
|
66
74
|
tooltipPosition={tooltipPosition}
|
|
75
|
+
showProportionText={showProportionText}
|
|
76
|
+
colorScale={colorScale}
|
|
67
77
|
/>
|
|
68
78
|
</div>
|
|
69
79
|
);
|
|
@@ -76,6 +86,33 @@ const MutationsOverTimeGrid: FunctionComponent<MutationsOverTimeGridProps> = ({
|
|
|
76
86
|
);
|
|
77
87
|
};
|
|
78
88
|
|
|
89
|
+
function useShowProportion(
|
|
90
|
+
gridRef: RefObject<HTMLDivElement>,
|
|
91
|
+
girdColumns: number,
|
|
92
|
+
setShowProportionText: (value: ((prevState: boolean) => boolean) | boolean) => void,
|
|
93
|
+
) {
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
const checkWidth = () => {
|
|
96
|
+
if (gridRef.current) {
|
|
97
|
+
const width = gridRef.current.getBoundingClientRect().width;
|
|
98
|
+
const widthPerDate = (width - remToPx(MUTATION_CELL_WIDTH_REM)) / girdColumns;
|
|
99
|
+
const maxWidthProportionText = 28;
|
|
100
|
+
|
|
101
|
+
setShowProportionText(widthPerDate > maxWidthProportionText);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
checkWidth();
|
|
106
|
+
window.addEventListener('resize', checkWidth);
|
|
107
|
+
|
|
108
|
+
return () => {
|
|
109
|
+
window.removeEventListener('resize', checkWidth);
|
|
110
|
+
};
|
|
111
|
+
}, [girdColumns, gridRef, setShowProportionText]);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const remToPx = (rem: number) => rem * parseFloat(getComputedStyle(document.documentElement).fontSize);
|
|
115
|
+
|
|
79
116
|
function getTooltipPosition(rowIndex: number, rows: number, columnIndex: number, columns: number) {
|
|
80
117
|
const tooltipX = rowIndex < rows / 2 ? 'bottom' : 'top';
|
|
81
118
|
const tooltipY = columnIndex < columns / 2 ? 'start' : 'end';
|
|
@@ -87,12 +124,15 @@ const ProportionCell: FunctionComponent<{
|
|
|
87
124
|
date: Temporal;
|
|
88
125
|
mutation: Substitution | Deletion;
|
|
89
126
|
tooltipPosition: TooltipPosition;
|
|
90
|
-
|
|
127
|
+
showProportionText: boolean;
|
|
128
|
+
colorScale: ColorScale;
|
|
129
|
+
}> = ({ value, mutation, date, tooltipPosition, showProportionText, colorScale }) => {
|
|
91
130
|
const tooltipContent = (
|
|
92
131
|
<div>
|
|
93
132
|
<p>
|
|
94
|
-
<span className='font-bold'>{date.englishName()}</span>
|
|
133
|
+
<span className='font-bold'>{date.englishName()}</span>
|
|
95
134
|
</p>
|
|
135
|
+
<p>({timeIntervalDisplay(date)})</p>
|
|
96
136
|
<p>{mutation.code}</p>
|
|
97
137
|
<p>Proportion: {formatProportion(value.proportion)}</p>
|
|
98
138
|
<p>Count: {value.count}</p>
|
|
@@ -100,21 +140,19 @@ const ProportionCell: FunctionComponent<{
|
|
|
100
140
|
);
|
|
101
141
|
|
|
102
142
|
return (
|
|
103
|
-
|
|
104
|
-
<
|
|
105
|
-
<
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
</div>
|
|
117
|
-
</>
|
|
143
|
+
<div className={'py-1 w-full h-full'}>
|
|
144
|
+
<Tooltip content={tooltipContent} position={tooltipPosition}>
|
|
145
|
+
<div
|
|
146
|
+
style={{
|
|
147
|
+
backgroundColor: getColorWithingScale(value.proportion, colorScale),
|
|
148
|
+
color: getTextColorForScale(value.proportion, colorScale),
|
|
149
|
+
}}
|
|
150
|
+
className={`w-full h-full text-center hover:font-bold text-xs group`}
|
|
151
|
+
>
|
|
152
|
+
{showProportionText ? formatProportion(value.proportion, 0) : undefined}
|
|
153
|
+
</div>
|
|
154
|
+
</Tooltip>
|
|
155
|
+
</div>
|
|
118
156
|
);
|
|
119
157
|
};
|
|
120
158
|
|
|
@@ -126,19 +164,6 @@ const timeIntervalDisplay = (date: Temporal) => {
|
|
|
126
164
|
return `${date.firstDay.toString()} - ${date.lastDay.toString()}`;
|
|
127
165
|
};
|
|
128
166
|
|
|
129
|
-
const backgroundColor = (proportion: number) => {
|
|
130
|
-
// TODO(#353): Make minAlpha and maxAlpha configurable
|
|
131
|
-
const minAlpha = 0.0;
|
|
132
|
-
const maxAlpha = 1;
|
|
133
|
-
|
|
134
|
-
const alpha = minAlpha + (maxAlpha - minAlpha) * proportion;
|
|
135
|
-
return singleGraphColorRGBByName('indigo', alpha);
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
const textColor = (proportion: number) => {
|
|
139
|
-
return proportion > 0.5 ? 'white' : 'black';
|
|
140
|
-
};
|
|
141
|
-
|
|
142
167
|
const MutationCell: FunctionComponent<{ mutation: Substitution | Deletion }> = ({ mutation }) => {
|
|
143
168
|
return <div className='text-center'>{mutation.toString()}</div>;
|
|
144
169
|
};
|
|
@@ -11,6 +11,8 @@ import { type LapisFilter, type SequenceType, type TemporalGranularity } from '.
|
|
|
11
11
|
import { compareTemporal } from '../../utils/temporal';
|
|
12
12
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
13
13
|
import { type DisplayedSegment, SegmentSelector, useDisplayedSegments } from '../components/SegmentSelector';
|
|
14
|
+
import { type ColorScale } from '../components/color-scale-selector';
|
|
15
|
+
import { ColorScaleSelectorDropdown } from '../components/color-scale-selector-dropdown';
|
|
14
16
|
import { CsvDownloadButton } from '../components/csv-download-button';
|
|
15
17
|
import { ErrorBoundary } from '../components/error-boundary';
|
|
16
18
|
import { ErrorDisplay } from '../components/error-display';
|
|
@@ -90,6 +92,7 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
|
|
|
90
92
|
views,
|
|
91
93
|
}) => {
|
|
92
94
|
const [proportionInterval, setProportionInterval] = useState({ min: 0.05, max: 0.9 });
|
|
95
|
+
const [colorScale, setColorScale] = useState<ColorScale>({ min: 0, max: 1, color: 'indigo' });
|
|
93
96
|
|
|
94
97
|
const [displayedSegments, setDisplayedSegments] = useDisplayedSegments(sequenceType);
|
|
95
98
|
const [displayedMutationTypes, setDisplayedMutationTypes] = useState<DisplayedMutationType[]>([
|
|
@@ -113,15 +116,16 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
|
|
|
113
116
|
case 'grid':
|
|
114
117
|
return {
|
|
115
118
|
title: 'Grid',
|
|
116
|
-
content: <MutationsOverTimeGrid data={filteredData} />,
|
|
119
|
+
content: <MutationsOverTimeGrid data={filteredData} colorScale={colorScale} />,
|
|
117
120
|
};
|
|
118
121
|
}
|
|
119
122
|
};
|
|
120
123
|
|
|
121
124
|
const tabs = views.map((view) => getTab(view));
|
|
122
125
|
|
|
123
|
-
const toolbar = () => (
|
|
126
|
+
const toolbar = (activeTab: string) => (
|
|
124
127
|
<Toolbar
|
|
128
|
+
activeTab={activeTab}
|
|
125
129
|
displayedSegments={displayedSegments}
|
|
126
130
|
setDisplayedSegments={setDisplayedSegments}
|
|
127
131
|
displayedMutationTypes={displayedMutationTypes}
|
|
@@ -129,6 +133,8 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
|
|
|
129
133
|
proportionInterval={proportionInterval}
|
|
130
134
|
setProportionInterval={setProportionInterval}
|
|
131
135
|
filteredData={filteredData}
|
|
136
|
+
colorScale={colorScale}
|
|
137
|
+
setColorScale={setColorScale}
|
|
132
138
|
/>
|
|
133
139
|
);
|
|
134
140
|
|
|
@@ -136,6 +142,7 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
|
|
|
136
142
|
};
|
|
137
143
|
|
|
138
144
|
type ToolbarProps = {
|
|
145
|
+
activeTab: string;
|
|
139
146
|
displayedSegments: DisplayedSegment[];
|
|
140
147
|
setDisplayedSegments: (segments: DisplayedSegment[]) => void;
|
|
141
148
|
displayedMutationTypes: DisplayedMutationType[];
|
|
@@ -143,9 +150,12 @@ type ToolbarProps = {
|
|
|
143
150
|
proportionInterval: ProportionInterval;
|
|
144
151
|
setProportionInterval: Dispatch<StateUpdater<ProportionInterval>>;
|
|
145
152
|
filteredData: MutationOverTimeDataGroupedByMutation;
|
|
153
|
+
colorScale: ColorScale;
|
|
154
|
+
setColorScale: Dispatch<StateUpdater<ColorScale>>;
|
|
146
155
|
};
|
|
147
156
|
|
|
148
157
|
const Toolbar: FunctionComponent<ToolbarProps> = ({
|
|
158
|
+
activeTab,
|
|
149
159
|
displayedSegments,
|
|
150
160
|
setDisplayedSegments,
|
|
151
161
|
displayedMutationTypes,
|
|
@@ -153,9 +163,14 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
|
|
|
153
163
|
proportionInterval,
|
|
154
164
|
setProportionInterval,
|
|
155
165
|
filteredData,
|
|
166
|
+
colorScale,
|
|
167
|
+
setColorScale,
|
|
156
168
|
}) => {
|
|
157
169
|
return (
|
|
158
170
|
<>
|
|
171
|
+
{activeTab === 'Grid' && (
|
|
172
|
+
<ColorScaleSelectorDropdown colorScale={colorScale} setColorScale={setColorScale} />
|
|
173
|
+
)}
|
|
159
174
|
<SegmentSelector displayedSegments={displayedSegments} setDisplayedSegments={setDisplayedSegments} />
|
|
160
175
|
<MutationTypeSelector
|
|
161
176
|
setDisplayedMutationTypes={setDisplayedMutationTypes}
|
|
@@ -171,7 +186,7 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
|
|
|
171
186
|
getData={() => getDownloadData(filteredData)}
|
|
172
187
|
filename='mutations_over_time.csv'
|
|
173
188
|
/>
|
|
174
|
-
<Info
|
|
189
|
+
<Info>Info for mutations over time</Info>
|
|
175
190
|
</>
|
|
176
191
|
);
|
|
177
192
|
};
|
|
@@ -156,7 +156,7 @@ const Toolbar = ({ activeTab, data, granularity, yAxisScaleType, setYAxisScaleTy
|
|
|
156
156
|
};
|
|
157
157
|
|
|
158
158
|
const NumberSequencesOverTimeInfo = () => (
|
|
159
|
-
<Info
|
|
159
|
+
<Info>
|
|
160
160
|
<InfoHeadline1>Number of sequences over time</InfoHeadline1>
|
|
161
161
|
<InfoParagraph>
|
|
162
162
|
<a href='https://github.com/GenSpectrum/dashboard-components/issues/315'>TODO</a>
|
|
@@ -236,7 +236,7 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
|
|
|
236
236
|
|
|
237
237
|
const PrevalenceOverTimeInfo: FunctionComponent = () => {
|
|
238
238
|
return (
|
|
239
|
-
<Info
|
|
239
|
+
<Info>
|
|
240
240
|
<InfoHeadline1>Prevalence over time</InfoHeadline1>
|
|
241
241
|
<InfoParagraph>Prevalence over time info.</InfoParagraph>
|
|
242
242
|
</Info>
|
|
@@ -12,7 +12,7 @@ const ColorsRGB = {
|
|
|
12
12
|
purple: [170, 68, 153],
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
type GraphColor = keyof typeof ColorsRGB;
|
|
15
|
+
export type GraphColor = keyof typeof ColorsRGB;
|
|
16
16
|
|
|
17
17
|
export const singleGraphColorRGBAById = (id: number, alpha = 1) => {
|
|
18
18
|
const keys = Object.keys(ColorsRGB) as GraphColor[];
|
|
@@ -475,3 +475,11 @@ export const AminoAcidMutationsByDay: StoryObj<Required<MutationsOverTimeProps>>
|
|
|
475
475
|
},
|
|
476
476
|
},
|
|
477
477
|
};
|
|
478
|
+
|
|
479
|
+
export const HideProportionOnSmallScreen: StoryObj<Required<MutationsOverTimeProps>> = {
|
|
480
|
+
...ByMonth,
|
|
481
|
+
args: {
|
|
482
|
+
...ByMonth.args,
|
|
483
|
+
width: '300px',
|
|
484
|
+
},
|
|
485
|
+
};
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { FunctionComponent } from 'preact';
|
|
2
|
-
|
|
3
|
-
export const DeleteIcon: FunctionComponent = () => {
|
|
4
|
-
return (
|
|
5
|
-
<svg
|
|
6
|
-
fill='currentColor'
|
|
7
|
-
stroke-width='0'
|
|
8
|
-
xmlns='http://www.w3.org/2000/svg'
|
|
9
|
-
viewBox='0 0 16 16'
|
|
10
|
-
style='overflow: visible; color: currentcolor;'
|
|
11
|
-
height='1em'
|
|
12
|
-
width='1em'
|
|
13
|
-
>
|
|
14
|
-
<path d='M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z' />
|
|
15
|
-
</svg>
|
|
16
|
-
);
|
|
17
|
-
};
|