@genspectrum/dashboard-components 0.15.0 → 0.16.1

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 (86) hide show
  1. package/custom-elements.json +255 -57
  2. package/dist/components.d.ts +49 -32
  3. package/dist/components.js +361 -212
  4. package/dist/components.js.map +1 -1
  5. package/dist/style.css +9 -0
  6. package/dist/util.d.ts +43 -43
  7. package/package.json +1 -1
  8. package/src/preact/ReferenceGenomeContext.ts +16 -1
  9. package/src/preact/aggregatedData/aggregate-bar-chart.tsx +26 -5
  10. package/src/preact/aggregatedData/aggregate.stories.tsx +0 -1
  11. package/src/preact/aggregatedData/aggregate.tsx +5 -1
  12. package/src/preact/components/ReferenceGenomesAwaiter.tsx +1 -6
  13. package/src/preact/components/info.tsx +1 -0
  14. package/src/preact/components/resize-container.tsx +1 -1
  15. package/src/preact/mutationComparison/mutation-comparison-venn.tsx +4 -2
  16. package/src/preact/mutationComparison/mutation-comparison.stories.tsx +0 -1
  17. package/src/preact/mutationComparison/mutation-comparison.tsx +5 -1
  18. package/src/preact/mutationFilter/mutation-filter.stories.tsx +17 -1
  19. package/src/preact/mutationFilter/mutation-filter.tsx +8 -0
  20. package/src/preact/mutations/mutations.stories.tsx +0 -1
  21. package/src/preact/mutations/mutations.tsx +1 -1
  22. package/src/preact/mutationsOverTime/getFilteredMutationsOverTime.spec.ts +70 -14
  23. package/src/preact/mutationsOverTime/getFilteredMutationsOverTimeData.ts +30 -7
  24. package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +56 -55
  25. package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +26 -39
  26. package/src/preact/mutationsOverTime/mutations-over-time.tsx +22 -7
  27. package/src/preact/numberSequencesOverTime/number-sequences-over-time-bar-chart.tsx +8 -3
  28. package/src/preact/numberSequencesOverTime/number-sequences-over-time-line-chart.tsx +8 -3
  29. package/src/preact/numberSequencesOverTime/number-sequences-over-time.stories.tsx +3 -1
  30. package/src/preact/numberSequencesOverTime/number-sequences-over-time.tsx +18 -3
  31. package/src/preact/prevalenceOverTime/prevalence-over-time-bar-chart.tsx +48 -35
  32. package/src/preact/prevalenceOverTime/prevalence-over-time-bubble-chart.tsx +83 -70
  33. package/src/preact/prevalenceOverTime/prevalence-over-time-line-chart.tsx +48 -37
  34. package/src/preact/prevalenceOverTime/prevalence-over-time.stories.tsx +0 -3
  35. package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +6 -1
  36. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage-chart.tsx +31 -23
  37. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.stories.tsx +0 -1
  38. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +5 -1
  39. package/src/preact/sequencesByLocation/__mockData__/worldAtlas.json +1 -0
  40. package/src/preact/{map → sequencesByLocation}/sequences-by-location-map.tsx +6 -3
  41. package/src/preact/{map → sequencesByLocation}/sequences-by-location-table.tsx +1 -1
  42. package/src/preact/{map → sequencesByLocation}/sequences-by-location.stories.tsx +58 -1
  43. package/src/preact/{map → sequencesByLocation}/sequences-by-location.tsx +10 -1
  44. package/src/preact/shared/aspectRatio/AspectRatio.tsx +13 -0
  45. package/src/preact/shared/charts/getMaintainAspectRatio.ts +3 -0
  46. package/src/preact/statistic/statistics.stories.tsx +0 -1
  47. package/src/preact/statistic/statistics.tsx +4 -4
  48. package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.stories.tsx +0 -1
  49. package/src/preact/wastewater/mutationsOverTime/wastewater-mutations-over-time.tsx +1 -1
  50. package/src/query/computeMapLocationData.spec.ts +1 -1
  51. package/src/query/computeMapLocationData.ts +1 -1
  52. package/src/query/querySequencesByLocationData.ts +1 -1
  53. package/src/utilEntrypoint.ts +1 -1
  54. package/src/web-components/PreactLitAdapter.tsx +2 -5
  55. package/src/web-components/ResizeContainer.mdx +4 -1
  56. package/src/web-components/gs-app.ts +2 -4
  57. package/src/web-components/visualization/gs-aggregate.stories.ts +13 -6
  58. package/src/web-components/visualization/gs-aggregate.tsx +1 -1
  59. package/src/web-components/visualization/gs-mutation-comparison.stories.ts +8 -1
  60. package/src/web-components/visualization/gs-mutation-comparison.tsx +1 -1
  61. package/src/web-components/visualization/gs-mutations-over-time.stories.ts +24 -1
  62. package/src/web-components/visualization/gs-mutations-over-time.tsx +30 -1
  63. package/src/web-components/visualization/gs-mutations.stories.ts +8 -1
  64. package/src/web-components/visualization/gs-mutations.tsx +1 -1
  65. package/src/web-components/visualization/gs-number-sequences-over-time.stories.ts +11 -1
  66. package/src/web-components/visualization/gs-number-sequences-over-time.tsx +1 -1
  67. package/src/web-components/visualization/gs-prevalence-over-time.stories.ts +8 -2
  68. package/src/web-components/visualization/gs-prevalence-over-time.tsx +1 -1
  69. package/src/web-components/visualization/gs-relative-growth-advantage.stories.ts +8 -1
  70. package/src/web-components/visualization/gs-relative-growth-advantage.tsx +1 -1
  71. package/src/web-components/visualization/gs-sequences-by-location.stories.ts +13 -7
  72. package/src/web-components/visualization/gs-sequences-by-location.tsx +6 -3
  73. package/src/web-components/visualization/gs-statistics.stories.ts +0 -1
  74. package/src/web-components/visualization/gs-statistics.tsx +1 -1
  75. package/src/web-components/wastewaterVisualization/gs-wastewater-mutations-over-time.stories.ts +9 -1
  76. package/src/web-components/wastewaterVisualization/gs-wastewater-mutations-over-time.tsx +1 -1
  77. package/standalone-bundle/dashboard-components.js +5817 -5706
  78. package/standalone-bundle/dashboard-components.js.map +1 -1
  79. package/standalone-bundle/style.css +1 -1
  80. package/src/preact/map/__mockData__/worldAtlas.json +0 -497127
  81. /package/src/preact/{map → sequencesByLocation}/__mockData__/aggregatedGermany.json +0 -0
  82. /package/src/preact/{map → sequencesByLocation}/__mockData__/aggregatedWorld.json +0 -0
  83. /package/src/preact/{map → sequencesByLocation}/__mockData__/germanyMap.json +0 -0
  84. /package/src/preact/{map → sequencesByLocation}/__mockData__/howToGenerateWorldMap.md +0 -0
  85. /package/src/preact/{map → sequencesByLocation}/leafletStyleModifications.css +0 -0
  86. /package/src/preact/{map → sequencesByLocation}/loadMapSource.tsx +0 -0
@@ -21,6 +21,7 @@ import { ProportionSelectorDropdown } from '../components/proportion-selector-dr
21
21
  import { ResizeContainer } from '../components/resize-container';
22
22
  import { type DisplayedSegment, SegmentSelector, useDisplayedSegments } from '../components/segment-selector';
23
23
  import Tabs from '../components/tabs';
24
+ import { getMaintainAspectRatio } from '../shared/charts/getMaintainAspectRatio';
24
25
  import { useQuery } from '../useQuery';
25
26
 
26
27
  export const mutationComparisonViewSchema = z.union([z.literal(views.table), z.literal(views.venn)]);
@@ -28,7 +29,7 @@ export type MutationComparisonView = z.infer<typeof mutationComparisonViewSchema
28
29
 
29
30
  const mutationComparisonPropsSchema = z.object({
30
31
  width: z.string(),
31
- height: z.string(),
32
+ height: z.string().optional(),
32
33
  lapisFilters: z.array(namedLapisFilterSchema).min(1),
33
34
  sequenceType: sequenceTypeSchema,
34
35
  views: z.array(mutationComparisonViewSchema),
@@ -91,6 +92,8 @@ const MutationComparisonTabs: FunctionComponent<MutationComparisonTabsProps> = (
91
92
  [data, displayedSegments, displayedMutationTypes],
92
93
  );
93
94
 
95
+ const maintainAspectRatio = getMaintainAspectRatio(originalComponentProps.height);
96
+
94
97
  const getTab = (view: MutationComparisonView) => {
95
98
  switch (view) {
96
99
  case 'table':
@@ -111,6 +114,7 @@ const MutationComparisonTabs: FunctionComponent<MutationComparisonTabsProps> = (
111
114
  <MutationComparisonVenn
112
115
  data={{ content: filteredData }}
113
116
  proportionInterval={proportionInterval}
117
+ maintainAspectRatio={maintainAspectRatio}
114
118
  />
115
119
  ),
116
120
  };
@@ -1,4 +1,4 @@
1
- import { type PreactRenderer, type Meta, type StoryObj } from '@storybook/preact';
1
+ import { type Meta, type PreactRenderer, type StoryObj } from '@storybook/preact';
2
2
  import { expect, fireEvent, fn, userEvent, waitFor, within } from '@storybook/test';
3
3
  import { type StepFunction } from '@storybook/types';
4
4
 
@@ -8,6 +8,7 @@ import { LAPIS_URL } from '../../constants';
8
8
  import referenceGenome from '../../lapisApi/__mockData__/referenceGenome.json';
9
9
  import { LapisUrlContextProvider } from '../LapisUrlContext';
10
10
  import { ReferenceGenomeContext } from '../ReferenceGenomeContext';
11
+ import { playThatExpectsErrorMessage } from '../shared/stories/expectErrorMessage';
11
12
 
12
13
  const meta: Meta<MutationFilterProps> = {
13
14
  title: 'Input/MutationFilter',
@@ -219,6 +220,21 @@ export const WithInitialValue: StoryObj<MutationFilterProps> = {
219
220
  },
220
221
  };
221
222
 
223
+ export const WithNoReferenceSequencesDefined: StoryObj<MutationFilterProps> = {
224
+ ...Default,
225
+ render: (args) => (
226
+ <LapisUrlContextProvider value={LAPIS_URL}>
227
+ <ReferenceGenomeContext.Provider value={{ nucleotideSequences: [], genes: [] }}>
228
+ <MutationFilter {...args} />
229
+ </ReferenceGenomeContext.Provider>
230
+ </LapisUrlContextProvider>
231
+ ),
232
+ play: playThatExpectsErrorMessage(
233
+ 'Error - No reference sequences available',
234
+ 'This organism has neither nucleotide nor amino acid sequences',
235
+ ),
236
+ };
237
+
222
238
  async function prepare(canvasElement: HTMLElement, step: StepFunction<PreactRenderer, unknown>) {
223
239
  const canvas = within(canvasElement);
224
240
 
@@ -10,6 +10,7 @@ import { type MutationsFilter, mutationsFilterSchema } from '../../types';
10
10
  import { type DeletionClass, type InsertionClass, type SubstitutionClass } from '../../utils/mutations';
11
11
  import { ReferenceGenomeContext } from '../ReferenceGenomeContext';
12
12
  import { ErrorBoundary } from '../components/error-boundary';
13
+ import { UserFacingError } from '../components/error-display';
13
14
  import { singleGraphColorRGBByName } from '../shared/charts/colors';
14
15
 
15
16
  const mutationFilterInnerPropsSchema = z.object({
@@ -54,6 +55,13 @@ export const MutationFilterInner: FunctionComponent<MutationFilterInnerProps> =
54
55
 
55
56
  const filterRef = useRef<HTMLDivElement>(null);
56
57
 
58
+ if (referenceGenome.nucleotideSequences.length === 0 && referenceGenome.genes.length === 0) {
59
+ throw new UserFacingError(
60
+ 'No reference sequences available',
61
+ 'This organism has neither nucleotide nor amino acid sequences configured in its reference genome. You cannot filter by mutations.',
62
+ );
63
+ }
64
+
57
65
  const handleRemoveValue = (option: ParsedMutationFilter) => {
58
66
  const newSelectedFilters = {
59
67
  ...selectedFilters,
@@ -55,7 +55,6 @@ export const Default: StoryObj<MutationsProps> = {
55
55
  sequenceType: 'nucleotide',
56
56
  views: ['grid', 'table', 'insertions'],
57
57
  width: '100%',
58
- height: '700px',
59
58
  pageSize: 10,
60
59
  },
61
60
  parameters: {
@@ -41,7 +41,7 @@ const mutationsPropsSchema = z.object({
41
41
  views: mutationsViewSchema.array(),
42
42
  pageSize: z.union([z.boolean(), z.number()]),
43
43
  width: z.string(),
44
- height: z.string(),
44
+ height: z.string().optional(),
45
45
  });
46
46
  export type MutationsProps = z.infer<typeof mutationsPropsSchema>;
47
47
 
@@ -15,16 +15,17 @@ describe('getFilteredMutationOverTimeData', () => {
15
15
  someDeletionEntry,
16
16
  ]);
17
17
 
18
- const result = getFilteredMutationOverTimeData(
18
+ const result = getFilteredMutationOverTimeData({
19
19
  data,
20
20
  overallMutationData,
21
- [
21
+ displayedSegments: [
22
22
  { segment: 'someSegment', checked: false, label: 'Some Segment' },
23
23
  { segment: 'someOtherSegment', checked: true, label: 'Some Other Segment' },
24
24
  ],
25
- [],
25
+ displayedMutationTypes: [],
26
26
  proportionInterval,
27
- );
27
+ displayMutations: undefined,
28
+ });
28
29
 
29
30
  expect(result.getFirstAxisKeys()).to.deep.equal([anotherSubstitution]);
30
31
  });
@@ -36,11 +37,11 @@ describe('getFilteredMutationOverTimeData', () => {
36
37
  someDeletionEntry,
37
38
  ]);
38
39
 
39
- const result = getFilteredMutationOverTimeData(
40
+ const result = getFilteredMutationOverTimeData({
40
41
  data,
41
42
  overallMutationData,
42
- [],
43
- [
43
+ displayedSegments: [],
44
+ displayedMutationTypes: [
44
45
  {
45
46
  type: 'substitution',
46
47
  checked: false,
@@ -53,7 +54,7 @@ describe('getFilteredMutationOverTimeData', () => {
53
54
  },
54
55
  ],
55
56
  proportionInterval,
56
- );
57
+ });
57
58
 
58
59
  expect(result.getFirstAxisKeys()).to.deep.equal([someDeletion]);
59
60
  });
@@ -65,7 +66,13 @@ describe('getFilteredMutationOverTimeData', () => {
65
66
  { ...someDeletionEntry, proportion: inFilter },
66
67
  ]);
67
68
 
68
- const result = getFilteredMutationOverTimeData(data, overallMutationData, [], [], proportionInterval);
69
+ const result = getFilteredMutationOverTimeData({
70
+ data,
71
+ overallMutationData,
72
+ displayedSegments: [],
73
+ displayedMutationTypes: [],
74
+ proportionInterval,
75
+ });
69
76
 
70
77
  expect(result.getFirstAxisKeys()).to.deep.equal([anotherSubstitution, someDeletion]);
71
78
  });
@@ -77,7 +84,13 @@ describe('getFilteredMutationOverTimeData', () => {
77
84
  { ...someDeletionEntry, proportion: inFilter },
78
85
  ]);
79
86
 
80
- const result = getFilteredMutationOverTimeData(data, overallMutationData, [], [], proportionInterval);
87
+ const result = getFilteredMutationOverTimeData({
88
+ data,
89
+ overallMutationData,
90
+ displayedSegments: [],
91
+ displayedMutationTypes: [],
92
+ proportionInterval,
93
+ });
81
94
 
82
95
  expect(result.getFirstAxisKeys()).to.deep.equal([anotherSubstitution, someDeletion]);
83
96
  });
@@ -90,7 +103,13 @@ describe('getFilteredMutationOverTimeData', () => {
90
103
  ]);
91
104
  data.set(someSubstitution, someTemporal, { ...someMutationOverTimeValue, proportion: belowFilter });
92
105
 
93
- const result = getFilteredMutationOverTimeData(data, overallMutationData, [], [], proportionInterval);
106
+ const result = getFilteredMutationOverTimeData({
107
+ data,
108
+ overallMutationData,
109
+ displayedSegments: [],
110
+ displayedMutationTypes: [],
111
+ proportionInterval,
112
+ });
94
113
 
95
114
  expect(result.getFirstAxisKeys()).to.deep.equal([someSubstitution, anotherSubstitution, someDeletion]);
96
115
  });
@@ -103,7 +122,13 @@ describe('getFilteredMutationOverTimeData', () => {
103
122
  ]);
104
123
  data.set(someSubstitution, someTemporal, { ...someMutationOverTimeValue, proportion: aboveFilter });
105
124
 
106
- const result = getFilteredMutationOverTimeData(data, overallMutationData, [], [], proportionInterval);
125
+ const result = getFilteredMutationOverTimeData({
126
+ data,
127
+ overallMutationData,
128
+ displayedSegments: [],
129
+ displayedMutationTypes: [],
130
+ proportionInterval,
131
+ });
107
132
 
108
133
  expect(result.getFirstAxisKeys()).to.deep.equal([someSubstitution, anotherSubstitution, someDeletion]);
109
134
  });
@@ -114,7 +139,13 @@ describe('getFilteredMutationOverTimeData', () => {
114
139
  { ...anotherSubstitutionEntry, proportion: inFilter },
115
140
  { ...someDeletionEntry, proportion: inFilter },
116
141
  ]);
117
- const result = getFilteredMutationOverTimeData(data, overallMutationData, [], [], proportionInterval);
142
+ const result = getFilteredMutationOverTimeData({
143
+ data,
144
+ overallMutationData,
145
+ displayedSegments: [],
146
+ displayedMutationTypes: [],
147
+ proportionInterval,
148
+ });
118
149
 
119
150
  expect(result.getFirstAxisKeys()).to.deep.equal([someSubstitution, anotherSubstitution, someDeletion]);
120
151
  });
@@ -126,11 +157,36 @@ describe('getFilteredMutationOverTimeData', () => {
126
157
  { ...someDeletionEntry, proportion: inFilter },
127
158
  ]);
128
159
 
129
- const result = getFilteredMutationOverTimeData(data, overallMutationData, [], [], proportionInterval);
160
+ const result = getFilteredMutationOverTimeData({
161
+ data,
162
+ overallMutationData,
163
+ displayedSegments: [],
164
+ displayedMutationTypes: [],
165
+ proportionInterval,
166
+ });
130
167
 
131
168
  expect(result.getFirstAxisKeys()).to.deep.equal([someSubstitution, anotherSubstitution, someDeletion]);
132
169
  });
133
170
 
171
+ it('should filter by displayMutations', () => {
172
+ const { data, overallMutationData } = prepareMutationOverTimeData([
173
+ someSubstitutionEntry,
174
+ anotherSubstitutionEntry,
175
+ someDeletionEntry,
176
+ ]);
177
+
178
+ const result = getFilteredMutationOverTimeData({
179
+ data,
180
+ overallMutationData,
181
+ displayedSegments: [],
182
+ displayedMutationTypes: [],
183
+ proportionInterval,
184
+ displayMutations: [anotherSubstitution.code, someDeletion.code],
185
+ });
186
+
187
+ expect(result.getFirstAxisKeys()).to.deep.equal([anotherSubstitution, someDeletion]);
188
+ });
189
+
134
190
  const belowFilter = 0.1;
135
191
  const atFilterMin = 0.2;
136
192
  const inFilter = 0.5;
@@ -1,3 +1,5 @@
1
+ import z from 'zod';
2
+
1
3
  import { type MutationOverTimeDataMap } from './MutationOverTimeData';
2
4
  import { type SubstitutionOrDeletionEntry } from '../../types';
3
5
  import { Map2dView } from '../../utils/map2d';
@@ -5,15 +7,31 @@ import type { Deletion, Substitution } from '../../utils/mutations';
5
7
  import type { DisplayedMutationType } from '../components/mutation-type-selector';
6
8
  import type { DisplayedSegment } from '../components/segment-selector';
7
9
 
8
- export function getFilteredMutationOverTimeData(
9
- data: MutationOverTimeDataMap,
10
- overallMutationData: SubstitutionOrDeletionEntry<Substitution, Deletion>[],
11
- displayedSegments: DisplayedSegment[],
12
- displayedMutationTypes: DisplayedMutationType[],
13
- proportionInterval: { min: number; max: number },
14
- ) {
10
+ export const displayMutationsSchema = z.array(z.string()).min(1);
11
+ export type DisplayMutations = z.infer<typeof displayMutationsSchema>;
12
+
13
+ export type GetFilteredMutationOverTimeDataArgs = {
14
+ data: MutationOverTimeDataMap;
15
+ overallMutationData: SubstitutionOrDeletionEntry<Substitution, Deletion>[];
16
+ displayedSegments: DisplayedSegment[];
17
+ displayedMutationTypes: DisplayedMutationType[];
18
+ proportionInterval: { min: number; max: number };
19
+ displayMutations?: DisplayMutations;
20
+ };
21
+
22
+ export function getFilteredMutationOverTimeData({
23
+ data,
24
+ overallMutationData,
25
+ displayedSegments,
26
+ displayedMutationTypes,
27
+ proportionInterval,
28
+ displayMutations,
29
+ }: GetFilteredMutationOverTimeDataArgs) {
15
30
  const filteredData = new Map2dView(data);
16
31
 
32
+ const displayMutationsSet =
33
+ displayMutations === undefined ? null : new Set(displayMutations.map((it) => it.toUpperCase()));
34
+
17
35
  const mutationsToFilterOut = overallMutationData.filter((entry) => {
18
36
  if (entry.proportion < proportionInterval.min || entry.proportion > proportionInterval.max) {
19
37
  return true;
@@ -21,6 +39,11 @@ export function getFilteredMutationOverTimeData(
21
39
  if (displayedSegments.some((segment) => segment.segment === entry.mutation.segment && !segment.checked)) {
22
40
  return true;
23
41
  }
42
+
43
+ if (displayMutationsSet !== null && !displayMutationsSet.has(entry.mutation.code)) {
44
+ return true;
45
+ }
46
+
24
47
  return displayedMutationTypes.some(
25
48
  (mutationType) => mutationType.type === entry.mutation.type && !mutationType.checked,
26
49
  );
@@ -39,63 +39,64 @@ const MutationsOverTimeGrid: FunctionComponent<MutationsOverTimeGridProps> = ({
39
39
  to reduce the number of mutations.
40
40
  </div>
41
41
  )}
42
- {allMutations.length === 0 && (
42
+ {allMutations.length === 0 ? (
43
43
  <div className={'flex justify-center'}>No data available for your filters.</div>
44
+ ) : (
45
+ <div
46
+ ref={gridRef}
47
+ style={{
48
+ display: 'grid',
49
+ gridTemplateRows: `repeat(${shownMutations.length}, 24px)`,
50
+ gridTemplateColumns: `${MUTATION_CELL_WIDTH_REM}rem repeat(${dates.length}, minmax(0.05rem, 1fr))`,
51
+ }}
52
+ className='text-center'
53
+ >
54
+ {dates.map((date, columnIndex) => (
55
+ <div
56
+ className='@container font-semibold'
57
+ style={{ gridRowStart: 1, gridColumnStart: columnIndex + 2 }}
58
+ key={date.dateString}
59
+ >
60
+ <p {...styleGridHeader(columnIndex, dates)}>{date.dateString}</p>
61
+ </div>
62
+ ))}
63
+ {shownMutations.map((mutation, rowIndex) => {
64
+ return (
65
+ <Fragment key={`fragment-${mutation.code}`}>
66
+ <div
67
+ key={`mutation-${mutation.code}`}
68
+ style={{ gridRowStart: rowIndex + 2, gridColumnStart: 1 }}
69
+ >
70
+ <MutationCell mutation={mutation} />
71
+ </div>
72
+ {dates.map((date, columnIndex) => {
73
+ const value = data.get(mutation, date) ?? null;
74
+ const tooltipPosition = getTooltipPosition(
75
+ rowIndex,
76
+ shownMutations.length,
77
+ columnIndex,
78
+ dates.length,
79
+ );
80
+ return (
81
+ <div
82
+ style={{ gridRowStart: rowIndex + 2, gridColumnStart: columnIndex + 2 }}
83
+ key={`${mutation.code}-${date.dateString}`}
84
+ >
85
+ <ProportionCell
86
+ value={value}
87
+ date={date}
88
+ mutation={mutation}
89
+ tooltipPosition={tooltipPosition}
90
+ colorScale={colorScale}
91
+ />
92
+ </div>
93
+ );
94
+ })}
95
+ </Fragment>
96
+ );
97
+ })}
98
+ </div>
44
99
  )}
45
- <div
46
- ref={gridRef}
47
- style={{
48
- display: 'grid',
49
- gridTemplateRows: `repeat(${shownMutations.length}, 24px)`,
50
- gridTemplateColumns: `${MUTATION_CELL_WIDTH_REM}rem repeat(${dates.length}, minmax(0.05rem, 1fr))`,
51
- }}
52
- className='text-center'
53
- >
54
- {dates.map((date, columnIndex) => (
55
- <div
56
- className='@container font-semibold'
57
- style={{ gridRowStart: 1, gridColumnStart: columnIndex + 2 }}
58
- key={date.dateString}
59
- >
60
- <p {...styleGridHeader(columnIndex, dates)}>{date.dateString}</p>
61
- </div>
62
- ))}
63
- {shownMutations.map((mutation, rowIndex) => {
64
- return (
65
- <Fragment key={`fragment-${mutation.code}`}>
66
- <div
67
- key={`mutation-${mutation.code}`}
68
- style={{ gridRowStart: rowIndex + 2, gridColumnStart: 1 }}
69
- >
70
- <MutationCell mutation={mutation} />
71
- </div>
72
- {dates.map((date, columnIndex) => {
73
- const value = data.get(mutation, date) ?? null;
74
- const tooltipPosition = getTooltipPosition(
75
- rowIndex,
76
- shownMutations.length,
77
- columnIndex,
78
- dates.length,
79
- );
80
- return (
81
- <div
82
- style={{ gridRowStart: rowIndex + 2, gridColumnStart: columnIndex + 2 }}
83
- key={`${mutation.code}-${date.dateString}`}
84
- >
85
- <ProportionCell
86
- value={value}
87
- date={date}
88
- mutation={mutation}
89
- tooltipPosition={tooltipPosition}
90
- colorScale={colorScale}
91
- />
92
- </div>
93
- );
94
- })}
95
- </Fragment>
96
- );
97
- })}
98
- </div>
99
100
  </>
100
101
  );
101
102
  };
@@ -28,6 +28,8 @@ const meta: Meta<MutationsOverTimeProps> = {
28
28
  control: { type: 'radio' },
29
29
  },
30
30
  lapisDateField: { control: 'text' },
31
+ displayMutations: { control: 'object' },
32
+ initialMeanProportionInterval: { control: 'object' },
31
33
  },
32
34
  parameters: {
33
35
  fetchMock: {},
@@ -36,49 +38,32 @@ const meta: Meta<MutationsOverTimeProps> = {
36
38
 
37
39
  export default meta;
38
40
 
39
- const Template = {
41
+ export const Default: StoryObj<MutationsOverTimeProps> = {
40
42
  render: (args: MutationsOverTimeProps) => (
41
43
  <LapisUrlContextProvider value={LAPIS_URL}>
42
44
  <ReferenceGenomeContext.Provider value={referenceGenome}>
43
- <MutationsOverTime
44
- lapisFilter={args.lapisFilter}
45
- sequenceType={args.sequenceType}
46
- views={args.views}
47
- width={args.width}
48
- height={args.height}
49
- granularity={args.granularity}
50
- lapisDateField={args.lapisDateField}
51
- />
45
+ <MutationsOverTime {...args} />
52
46
  </ReferenceGenomeContext.Provider>
53
47
  </LapisUrlContextProvider>
54
48
  ),
55
- };
56
-
57
- // This test uses mock data: defaultMockData.ts (through mutationOverTimeWorker.mock.ts)
58
- export const Default: StoryObj<MutationsOverTimeProps> = {
59
- ...Template,
60
49
  args: {
61
50
  lapisFilter: { pangoLineage: 'JN.1*', dateFrom: '2024-01-15', dateTo: '2024-07-10' },
62
51
  sequenceType: 'nucleotide',
63
52
  views: ['grid'],
64
53
  width: '100%',
65
- height: '700px',
66
54
  granularity: 'month',
67
55
  lapisDateField: 'date',
56
+ initialMeanProportionInterval: { min: 0.05, max: 0.9 },
68
57
  },
69
58
  };
70
59
 
71
60
  // This test uses mock data: showMessagWhenTooManyMutations.ts (through mutationOverTimeWorker.mock.ts)
72
61
  export const ShowsMessageWhenTooManyMutations: StoryObj<MutationsOverTimeProps> = {
73
- ...Template,
62
+ ...Default,
74
63
  args: {
64
+ ...Default.args,
75
65
  lapisFilter: { dateFrom: '2023-01-01', dateTo: '2023-12-31' },
76
- sequenceType: 'nucleotide',
77
- views: ['grid'],
78
- width: '100%',
79
- height: '700px',
80
66
  granularity: 'year',
81
- lapisDateField: 'date',
82
67
  },
83
68
  play: async ({ canvas }) => {
84
69
  await waitFor(() => expect(canvas.getByText('Showing 100 of 137 mutations.', { exact: false })).toBeVisible(), {
@@ -88,15 +73,12 @@ export const ShowsMessageWhenTooManyMutations: StoryObj<MutationsOverTimeProps>
88
73
  };
89
74
 
90
75
  export const ShowsNoDataWhenNoMutationsAreInFilter: StoryObj<MutationsOverTimeProps> = {
91
- ...Template,
76
+ ...Default,
92
77
  args: {
78
+ ...Default.args,
93
79
  lapisFilter: { dateFrom: '1800-01-01', dateTo: '1800-01-02' },
94
- sequenceType: 'nucleotide',
95
- views: ['grid'],
96
- width: '100%',
97
80
  height: '700px',
98
81
  granularity: 'year',
99
- lapisDateField: 'date',
100
82
  },
101
83
  play: async ({ canvas }) => {
102
84
  await waitFor(() => expect(canvas.getByText('No data available.', { exact: false })).toBeVisible(), {
@@ -106,15 +88,12 @@ export const ShowsNoDataWhenNoMutationsAreInFilter: StoryObj<MutationsOverTimePr
106
88
  };
107
89
 
108
90
  export const ShowsNoDataMessageWhenThereAreNoDatesInFilter: StoryObj<MutationsOverTimeProps> = {
109
- ...Template,
91
+ ...Default,
110
92
  args: {
93
+ ...Default.args,
111
94
  lapisFilter: { dateFrom: '2345-01-01', dateTo: '2020-01-02' },
112
- sequenceType: 'nucleotide',
113
- views: ['grid'],
114
- width: '100%',
115
95
  height: '700px',
116
96
  granularity: 'year',
117
- lapisDateField: 'date',
118
97
  },
119
98
  play: async ({ canvas }) => {
120
99
  await waitFor(() => expect(canvas.getByText('No data available.', { exact: false })).toBeVisible(), {
@@ -124,15 +103,10 @@ export const ShowsNoDataMessageWhenThereAreNoDatesInFilter: StoryObj<MutationsOv
124
103
  };
125
104
 
126
105
  export const ShowsNoDataMessageForStrictFilters: StoryObj<MutationsOverTimeProps> = {
127
- ...Template,
106
+ ...Default,
128
107
  args: {
108
+ ...Default.args,
129
109
  lapisFilter: { pangoLineage: 'JN.1*', dateFrom: '2024-01-15', dateTo: '2024-07-10' },
130
- sequenceType: 'nucleotide',
131
- views: ['grid'],
132
- width: '100%',
133
- height: '700px',
134
- granularity: 'month',
135
- lapisDateField: 'date',
136
110
  },
137
111
  play: async ({ canvas }) => {
138
112
  await waitFor(() => expect(canvas.getByText('Grid')).toBeVisible(), { timeout: 10000 });
@@ -157,6 +131,19 @@ export const ShowsNoDataMessageForStrictFilters: StoryObj<MutationsOverTimeProps
157
131
  },
158
132
  };
159
133
 
134
+ export const ShowsNoDataForStrictInitialProportionInterval: StoryObj<MutationsOverTimeProps> = {
135
+ ...ShowsNoDataMessageForStrictFilters,
136
+ args: {
137
+ ...ShowsNoDataMessageForStrictFilters.args,
138
+ initialMeanProportionInterval: { min: 0.4, max: 0.41 },
139
+ },
140
+ play: async ({ canvas }) => {
141
+ await waitFor(() =>
142
+ expect(canvas.getByText('No data available for your filters.', { exact: false })).toBeVisible(),
143
+ );
144
+ },
145
+ };
146
+
160
147
  export const WithNoLapisDateFieldField: StoryObj<MutationsOverTimeProps> = {
161
148
  ...Default,
162
149
  args: {
@@ -5,7 +5,7 @@ import z from 'zod';
5
5
  // @ts-expect-error -- uses subpath imports and vite worker import
6
6
  import MutationOverTimeWorker from '#mutationOverTime?worker&inline';
7
7
  import { BaseMutationOverTimeDataMap, type MutationOverTimeDataMap } from './MutationOverTimeData';
8
- import { getFilteredMutationOverTimeData } from './getFilteredMutationsOverTimeData';
8
+ import { displayMutationsSchema, getFilteredMutationOverTimeData } from './getFilteredMutationsOverTimeData';
9
9
  import { type MutationOverTimeWorkerResponse } from './mutationOverTimeWorker';
10
10
  import MutationsOverTimeGrid from './mutations-over-time-grid';
11
11
  import { type MutationOverTimeQuery } from '../../query/queryMutationsOverTime';
@@ -44,8 +44,13 @@ const mutationOverTimeSchema = z.object({
44
44
  views: z.array(mutationsOverTimeViewSchema),
45
45
  granularity: temporalGranularitySchema,
46
46
  lapisDateField: z.string().min(1),
47
+ displayMutations: displayMutationsSchema.optional(),
48
+ initialMeanProportionInterval: z.object({
49
+ min: z.number().min(0).max(1),
50
+ max: z.number().min(0).max(1),
51
+ }),
47
52
  width: z.string(),
48
- height: z.string(),
53
+ height: z.string().optional(),
49
54
  });
50
55
  export type MutationsOverTimeProps = z.infer<typeof mutationOverTimeSchema>;
51
56
 
@@ -116,7 +121,7 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
116
121
  originalComponentProps,
117
122
  overallMutationData,
118
123
  }) => {
119
- const [proportionInterval, setProportionInterval] = useState({ min: 0.05, max: 0.9 });
124
+ const [proportionInterval, setProportionInterval] = useState(originalComponentProps.initialMeanProportionInterval);
120
125
  const [colorScale, setColorScale] = useState<ColorScale>({ min: 0, max: 1, color: 'indigo' });
121
126
 
122
127
  const [displayedSegments, setDisplayedSegments] = useDisplayedSegments(originalComponentProps.sequenceType);
@@ -125,15 +130,25 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
125
130
  { label: 'Deletions', checked: true, type: 'deletion' },
126
131
  ]);
127
132
 
133
+ const displayMutations = originalComponentProps.displayMutations;
134
+
128
135
  const filteredData = useMemo(() => {
129
- return getFilteredMutationOverTimeData(
130
- mutationOverTimeData,
136
+ return getFilteredMutationOverTimeData({
137
+ data: mutationOverTimeData,
131
138
  overallMutationData,
132
139
  displayedSegments,
133
140
  displayedMutationTypes,
134
141
  proportionInterval,
135
- );
136
- }, [mutationOverTimeData, overallMutationData, displayedSegments, displayedMutationTypes, proportionInterval]);
142
+ displayMutations,
143
+ });
144
+ }, [
145
+ mutationOverTimeData,
146
+ overallMutationData,
147
+ displayedSegments,
148
+ displayedMutationTypes,
149
+ proportionInterval,
150
+ displayMutations,
151
+ ]);
137
152
 
138
153
  const getTab = (view: MutationsOverTimeView) => {
139
154
  if (filteredData === undefined) {