@genspectrum/dashboard-components 0.16.0 → 0.16.2

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/util.d.ts CHANGED
@@ -912,7 +912,7 @@ declare global {
912
912
 
913
913
  declare global {
914
914
  interface HTMLElementTagNameMap {
915
- 'gs-aggregate': AggregateComponent;
915
+ 'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
916
916
  }
917
917
  }
918
918
 
@@ -920,7 +920,7 @@ declare global {
920
920
  declare global {
921
921
  namespace JSX {
922
922
  interface IntrinsicElements {
923
- 'gs-aggregate': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
923
+ 'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
924
924
  }
925
925
  }
926
926
  }
@@ -928,7 +928,7 @@ declare global {
928
928
 
929
929
  declare global {
930
930
  interface HTMLElementTagNameMap {
931
- 'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
931
+ 'gs-aggregate': AggregateComponent;
932
932
  }
933
933
  }
934
934
 
@@ -936,7 +936,7 @@ declare global {
936
936
  declare global {
937
937
  namespace JSX {
938
938
  interface IntrinsicElements {
939
- 'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
939
+ 'gs-aggregate': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
940
940
  }
941
941
  }
942
942
  }
@@ -992,11 +992,10 @@ declare global {
992
992
 
993
993
  declare global {
994
994
  interface HTMLElementTagNameMap {
995
- 'gs-date-range-filter': DateRangeFilterComponent;
995
+ 'gs-location-filter': LocationFilterComponent;
996
996
  }
997
997
  interface HTMLElementEventMap {
998
- 'gs-date-range-filter-changed': CustomEvent<Record<string, string>>;
999
- 'gs-date-range-option-changed': DateRangeOptionChangedEvent;
998
+ 'gs-location-changed': LocationChangedEvent;
1000
999
  }
1001
1000
  }
1002
1001
 
@@ -1004,7 +1003,7 @@ declare global {
1004
1003
  declare global {
1005
1004
  namespace JSX {
1006
1005
  interface IntrinsicElements {
1007
- 'gs-date-range-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1006
+ 'gs-location-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1008
1007
  }
1009
1008
  }
1010
1009
  }
@@ -1012,10 +1011,11 @@ declare global {
1012
1011
 
1013
1012
  declare global {
1014
1013
  interface HTMLElementTagNameMap {
1015
- 'gs-location-filter': LocationFilterComponent;
1014
+ 'gs-date-range-filter': DateRangeFilterComponent;
1016
1015
  }
1017
1016
  interface HTMLElementEventMap {
1018
- 'gs-location-changed': LocationChangedEvent;
1017
+ 'gs-date-range-filter-changed': CustomEvent<Record<string, string>>;
1018
+ 'gs-date-range-option-changed': DateRangeOptionChangedEvent;
1019
1019
  }
1020
1020
  }
1021
1021
 
@@ -1023,7 +1023,7 @@ declare global {
1023
1023
  declare global {
1024
1024
  namespace JSX {
1025
1025
  interface IntrinsicElements {
1026
- 'gs-location-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1026
+ 'gs-date-range-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1027
1027
  }
1028
1028
  }
1029
1029
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genspectrum/dashboard-components",
3
- "version": "0.16.0",
3
+ "version": "0.16.2",
4
4
  "description": "GenSpectrum web components for building dashboards",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-only",
@@ -4,10 +4,12 @@ import { type ReferenceGenome } from '../lapisApi/ReferenceGenome';
4
4
 
5
5
  const UNINITIALIZED_SEQUENCE = '__uninitialized__';
6
6
 
7
- export const ReferenceGenomeContext = createContext<ReferenceGenome>({
7
+ export const INITIAL_REFERENCE_GENOMES = {
8
8
  nucleotideSequences: [{ name: UNINITIALIZED_SEQUENCE, sequence: '' }],
9
9
  genes: [],
10
- });
10
+ };
11
+
12
+ export const ReferenceGenomeContext = createContext<ReferenceGenome>(INITIAL_REFERENCE_GENOMES);
11
13
 
12
14
  export function isNotInitialized(referenceGenome: ReferenceGenome) {
13
15
  return (
@@ -1,6 +1,6 @@
1
1
  import { useCombobox } from 'downshift/preact';
2
2
  import { type ComponentChild } from 'preact';
3
- import { useRef, useState } from 'preact/hooks';
3
+ import { useMemo, useRef, useState } from 'preact/hooks';
4
4
 
5
5
  export function DownshiftCombobox<Item>({
6
6
  allItems,
@@ -21,8 +21,10 @@ export function DownshiftCombobox<Item>({
21
21
  }) {
22
22
  const initialSelectedItem = value ?? null;
23
23
 
24
- const [items, setItems] = useState(
25
- allItems.filter((item) => filterItemsByInputValue(item, itemToString(initialSelectedItem))),
24
+ const [itemsFilter, setItemsFilter] = useState(itemToString(initialSelectedItem));
25
+ const items = useMemo(
26
+ () => allItems.filter((item) => filterItemsByInputValue(item, itemsFilter)),
27
+ [allItems, filterItemsByInputValue, itemsFilter],
26
28
  );
27
29
  const divRef = useRef<HTMLDivElement>(null);
28
30
 
@@ -52,7 +54,7 @@ export function DownshiftCombobox<Item>({
52
54
  closeMenu,
53
55
  } = useCombobox({
54
56
  onInputValueChange({ inputValue }) {
55
- setItems(allItems.filter((item) => filterItemsByInputValue(item, inputValue)));
57
+ setItemsFilter(inputValue);
56
58
  },
57
59
  onSelectedItemChange({ selectedItem }) {
58
60
  if (selectedItem !== null) {
@@ -97,6 +97,7 @@ function componentParametersToCode(componentName: string, params: object, lapisU
97
97
 
98
98
  const attributes = indentLines(
99
99
  Object.entries(params)
100
+ .filter(([_, value]) => value !== undefined)
100
101
  .map(([key, value]) => `${key}='${stringifyIfNeeded(value) as string}'`)
101
102
  .join('\n'),
102
103
  4,
@@ -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,27 +38,14 @@ 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',
@@ -64,19 +53,17 @@ export const Default: StoryObj<MutationsOverTimeProps> = {
64
53
  width: '100%',
65
54
  granularity: 'month',
66
55
  lapisDateField: 'date',
56
+ initialMeanProportionInterval: { min: 0.05, max: 0.9 },
67
57
  },
68
58
  };
69
59
 
70
60
  // This test uses mock data: showMessagWhenTooManyMutations.ts (through mutationOverTimeWorker.mock.ts)
71
61
  export const ShowsMessageWhenTooManyMutations: StoryObj<MutationsOverTimeProps> = {
72
- ...Template,
62
+ ...Default,
73
63
  args: {
64
+ ...Default.args,
74
65
  lapisFilter: { dateFrom: '2023-01-01', dateTo: '2023-12-31' },
75
- sequenceType: 'nucleotide',
76
- views: ['grid'],
77
- width: '100%',
78
66
  granularity: 'year',
79
- lapisDateField: 'date',
80
67
  },
81
68
  play: async ({ canvas }) => {
82
69
  await waitFor(() => expect(canvas.getByText('Showing 100 of 137 mutations.', { exact: false })).toBeVisible(), {
@@ -86,15 +73,12 @@ export const ShowsMessageWhenTooManyMutations: StoryObj<MutationsOverTimeProps>
86
73
  };
87
74
 
88
75
  export const ShowsNoDataWhenNoMutationsAreInFilter: StoryObj<MutationsOverTimeProps> = {
89
- ...Template,
76
+ ...Default,
90
77
  args: {
78
+ ...Default.args,
91
79
  lapisFilter: { dateFrom: '1800-01-01', dateTo: '1800-01-02' },
92
- sequenceType: 'nucleotide',
93
- views: ['grid'],
94
- width: '100%',
95
80
  height: '700px',
96
81
  granularity: 'year',
97
- lapisDateField: 'date',
98
82
  },
99
83
  play: async ({ canvas }) => {
100
84
  await waitFor(() => expect(canvas.getByText('No data available.', { exact: false })).toBeVisible(), {
@@ -104,15 +88,12 @@ export const ShowsNoDataWhenNoMutationsAreInFilter: StoryObj<MutationsOverTimePr
104
88
  };
105
89
 
106
90
  export const ShowsNoDataMessageWhenThereAreNoDatesInFilter: StoryObj<MutationsOverTimeProps> = {
107
- ...Template,
91
+ ...Default,
108
92
  args: {
93
+ ...Default.args,
109
94
  lapisFilter: { dateFrom: '2345-01-01', dateTo: '2020-01-02' },
110
- sequenceType: 'nucleotide',
111
- views: ['grid'],
112
- width: '100%',
113
95
  height: '700px',
114
96
  granularity: 'year',
115
- lapisDateField: 'date',
116
97
  },
117
98
  play: async ({ canvas }) => {
118
99
  await waitFor(() => expect(canvas.getByText('No data available.', { exact: false })).toBeVisible(), {
@@ -122,15 +103,10 @@ export const ShowsNoDataMessageWhenThereAreNoDatesInFilter: StoryObj<MutationsOv
122
103
  };
123
104
 
124
105
  export const ShowsNoDataMessageForStrictFilters: StoryObj<MutationsOverTimeProps> = {
125
- ...Template,
106
+ ...Default,
126
107
  args: {
108
+ ...Default.args,
127
109
  lapisFilter: { pangoLineage: 'JN.1*', dateFrom: '2024-01-15', dateTo: '2024-07-10' },
128
- sequenceType: 'nucleotide',
129
- views: ['grid'],
130
- width: '100%',
131
- height: '700px',
132
- granularity: 'month',
133
- lapisDateField: 'date',
134
110
  },
135
111
  play: async ({ canvas }) => {
136
112
  await waitFor(() => expect(canvas.getByText('Grid')).toBeVisible(), { timeout: 10000 });
@@ -155,6 +131,19 @@ export const ShowsNoDataMessageForStrictFilters: StoryObj<MutationsOverTimeProps
155
131
  },
156
132
  };
157
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
+
158
147
  export const WithNoLapisDateFieldField: StoryObj<MutationsOverTimeProps> = {
159
148
  ...Default,
160
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,6 +44,11 @@ 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
53
  height: z.string().optional(),
49
54
  });
@@ -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) {