@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.
Files changed (27) hide show
  1. package/custom-elements.json +16 -0
  2. package/dist/dashboard-components.js +223 -142
  3. package/dist/dashboard-components.js.map +1 -1
  4. package/dist/genspectrum-components.d.ts +3 -3
  5. package/dist/style.css +42 -6
  6. package/package.json +1 -1
  7. package/src/preact/aggregatedData/aggregate.tsx +1 -1
  8. package/src/preact/components/color-scale-selector-dropdown.stories.tsx +27 -0
  9. package/src/preact/components/color-scale-selector-dropdown.tsx +17 -0
  10. package/src/preact/components/color-scale-selector.stories.tsx +61 -0
  11. package/src/preact/components/color-scale-selector.tsx +79 -0
  12. package/src/preact/components/info.stories.tsx +37 -10
  13. package/src/preact/components/info.tsx +22 -43
  14. package/src/preact/components/min-max-range-slider.tsx +1 -1
  15. package/src/preact/components/tooltip.tsx +2 -2
  16. package/src/preact/mutationComparison/mutation-comparison.tsx +1 -1
  17. package/src/preact/mutationFilter/mutation-filter-info.tsx +1 -1
  18. package/src/preact/mutationFilter/mutation-filter.stories.tsx +1 -1
  19. package/src/preact/mutationFilter/mutation-filter.tsx +2 -3
  20. package/src/preact/mutations/mutations.tsx +1 -1
  21. package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +59 -34
  22. package/src/preact/mutationsOverTime/mutations-over-time.tsx +18 -3
  23. package/src/preact/numberSequencesOverTime/number-sequences-over-time.tsx +1 -1
  24. package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +1 -1
  25. package/src/preact/shared/charts/colors.ts +1 -1
  26. package/src/web-components/visualization/gs-mutations-over-time.stories.ts +8 -0
  27. 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: `8rem repeat(${dates.length}, minmax(1.5rem, 1fr))`,
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
- }> = ({ value, mutation, date, tooltipPosition }) => {
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> ({timeIntervalDisplay(date)})
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
- <div className={'py-1'}>
105
- <Tooltip content={tooltipContent} position={tooltipPosition}>
106
- <div
107
- style={{
108
- backgroundColor: backgroundColor(value.proportion),
109
- color: textColor(value.proportion),
110
- }}
111
- className='text-center hover:font-bold text-xs'
112
- >
113
- {formatProportion(value.proportion, 0)}
114
- </div>
115
- </Tooltip>
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 height={'100px'}>Info for mutations over time</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 height='100px'>
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 height={'100px'}>
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
- };