@genspectrum/dashboard-components 0.1.3 → 0.1.5

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 (78) hide show
  1. package/custom-elements.json +488 -117
  2. package/dist/dashboard-components.js +904 -466
  3. package/dist/dashboard-components.js.map +1 -1
  4. package/dist/genspectrum-components.d.ts +473 -67
  5. package/dist/style.css +273 -153
  6. package/package.json +11 -7
  7. package/src/preact/aggregatedData/aggregate.stories.tsx +7 -5
  8. package/src/preact/aggregatedData/aggregate.tsx +16 -7
  9. package/src/preact/components/ReferenceGenomesAwaiter.tsx +25 -0
  10. package/src/preact/components/csv-download-button.tsx +8 -2
  11. package/src/preact/components/headline.stories.tsx +19 -1
  12. package/src/preact/components/headline.tsx +25 -5
  13. package/src/preact/components/info.stories.tsx +24 -3
  14. package/src/preact/components/info.tsx +49 -5
  15. package/src/preact/components/min-max-range-slider.tsx +4 -4
  16. package/src/preact/components/percent-intput.tsx +2 -3
  17. package/src/preact/components/resize-container.tsx +23 -0
  18. package/src/preact/components/table.tsx +1 -0
  19. package/src/preact/components/tabs.stories.tsx +2 -2
  20. package/src/preact/components/tabs.tsx +47 -24
  21. package/src/preact/dateRangeSelector/date-range-selector.stories.tsx +36 -4
  22. package/src/preact/dateRangeSelector/date-range-selector.tsx +67 -53
  23. package/src/preact/locationFilter/location-filter.tsx +2 -2
  24. package/src/preact/mutationComparison/getMutationComparisonTableData.spec.ts +5 -5
  25. package/src/preact/mutationComparison/getMutationComparisonTableData.ts +45 -10
  26. package/src/preact/mutationComparison/mutation-comparison-table.tsx +20 -22
  27. package/src/preact/mutationComparison/mutation-comparison-venn.tsx +6 -3
  28. package/src/preact/mutationComparison/mutation-comparison.stories.tsx +11 -1
  29. package/src/preact/mutationComparison/mutation-comparison.tsx +16 -7
  30. package/src/preact/mutationFilter/mutation-filter.stories.tsx +70 -31
  31. package/src/preact/mutationFilter/mutation-filter.tsx +62 -14
  32. package/src/preact/mutations/getInsertionsTableData.spec.ts +6 -4
  33. package/src/preact/mutations/getInsertionsTableData.ts +1 -1
  34. package/src/preact/mutations/getMutationsTableData.spec.ts +9 -19
  35. package/src/preact/mutations/getMutationsTableData.ts +1 -1
  36. package/src/preact/mutations/mutations-insertions-table.tsx +3 -1
  37. package/src/preact/mutations/mutations-table.tsx +3 -1
  38. package/src/preact/mutations/mutations.stories.tsx +11 -1
  39. package/src/preact/mutations/mutations.tsx +24 -7
  40. package/src/preact/prevalenceOverTime/prevalence-over-time-bar-chart.tsx +1 -0
  41. package/src/preact/prevalenceOverTime/prevalence-over-time-bubble-chart.tsx +1 -0
  42. package/src/preact/prevalenceOverTime/prevalence-over-time-line-chart.tsx +1 -0
  43. package/src/preact/prevalenceOverTime/prevalence-over-time.stories.tsx +8 -0
  44. package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +31 -13
  45. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage-chart.tsx +8 -5
  46. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.stories.tsx +15 -0
  47. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +62 -12
  48. package/src/preact/shared/sort/sortInsertions.spec.ts +11 -10
  49. package/src/preact/shared/sort/sortInsertions.ts +10 -17
  50. package/src/preact/shared/sort/sortSubstitutionsAndDeletions.spec.ts +19 -10
  51. package/src/preact/shared/sort/sortSubstitutionsAndDeletions.ts +45 -12
  52. package/src/preact/textInput/text-input.stories.tsx +22 -1
  53. package/src/preact/textInput/text-input.tsx +3 -1
  54. package/src/utils/typeAssertions.spec.ts +31 -0
  55. package/src/utils/typeAssertions.ts +16 -0
  56. package/src/web-components/PreactLitAdapter.tsx +0 -1
  57. package/src/web-components/app.stories.ts +129 -0
  58. package/src/web-components/app.ts +27 -6
  59. package/src/web-components/display/aggregate-component.stories.ts +24 -11
  60. package/src/web-components/display/aggregate-component.tsx +26 -5
  61. package/src/web-components/display/mutation-comparison-component.stories.ts +32 -11
  62. package/src/web-components/display/mutation-comparison-component.tsx +79 -4
  63. package/src/web-components/display/mutations-component.stories.ts +40 -19
  64. package/src/web-components/display/mutations-component.tsx +71 -4
  65. package/src/web-components/display/prevalence-over-time-component.stories.ts +44 -18
  66. package/src/web-components/display/prevalence-over-time-component.tsx +105 -5
  67. package/src/web-components/display/relative-growth-advantage-component.stories.ts +32 -10
  68. package/src/web-components/display/relative-growth-advantage-component.tsx +66 -3
  69. package/src/web-components/input/date-range-selector-component.stories.ts +51 -9
  70. package/src/web-components/input/date-range-selector-component.tsx +69 -4
  71. package/src/web-components/input/location-filter-component.stories.ts +15 -4
  72. package/src/web-components/input/location-filter-component.tsx +2 -6
  73. package/src/web-components/input/mutation-filter-component.stories.ts +33 -12
  74. package/src/web-components/input/mutation-filter-component.tsx +60 -4
  75. package/src/web-components/input/text-input-component.stories.ts +26 -6
  76. package/src/web-components/input/text-input-component.tsx +34 -3
  77. package/src/web-components/display/aggregate-component.mdx +0 -25
  78. package/src/web-components/input/location-filter.mdx +0 -25
@@ -1,6 +1,7 @@
1
1
  import { withActions } from '@storybook/addon-actions/decorator';
2
- import { type Meta, type StoryObj } from '@storybook/preact';
2
+ import { type Meta, type PreactRenderer, type StoryObj } from '@storybook/preact';
3
3
  import { expect, fireEvent, fn, userEvent, waitFor, within } from '@storybook/test';
4
+ import { type StepFunction } from '@storybook/types';
4
5
 
5
6
  import { MutationFilter, type MutationFilterProps } from './mutation-filter';
6
7
  import { LAPIS_URL } from '../../constants';
@@ -35,21 +36,11 @@ export const Default: StoryObj<MutationFilterProps> = {
35
36
  export const FiresFilterChangedEvents: StoryObj<MutationFilterProps> = {
36
37
  ...Default,
37
38
  play: async ({ canvasElement, step }) => {
38
- const canvas = within(canvasElement);
39
- const listenerMock = fn();
40
- await step('Setup event listener mock', async () => {
41
- canvasElement.addEventListener('gs-mutation-filter-changed', listenerMock);
42
- });
43
-
44
- await step('wait until data is loaded', async () => {
45
- await waitFor(() => {
46
- return expect(inputField(canvas)).toBeEnabled();
47
- });
48
- });
39
+ const { canvas, changedListenerMock } = await prepare(canvasElement, step);
49
40
 
50
41
  await step('Enters an invalid mutation', async () => {
51
42
  await submitMutation(canvas, 'notAMutation');
52
- await expect(listenerMock).not.toHaveBeenCalled();
43
+ await expect(changedListenerMock).not.toHaveBeenCalled();
53
44
 
54
45
  await userEvent.type(inputField(canvas), '{backspace>12/}');
55
46
  });
@@ -58,7 +49,7 @@ export const FiresFilterChangedEvents: StoryObj<MutationFilterProps> = {
58
49
  await submitMutation(canvas, 'A123T');
59
50
 
60
51
  await waitFor(() =>
61
- expect(listenerMock).toHaveBeenCalledWith(
52
+ expect(changedListenerMock).toHaveBeenCalledWith(
62
53
  expect.objectContaining({
63
54
  detail: {
64
55
  nucleotideMutations: ['A123T'],
@@ -74,7 +65,7 @@ export const FiresFilterChangedEvents: StoryObj<MutationFilterProps> = {
74
65
  await step('Enter a second valid nucleotide mutation', async () => {
75
66
  await submitMutation(canvas, 'A234-');
76
67
 
77
- await expect(listenerMock).toHaveBeenCalledWith(
68
+ await expect(changedListenerMock).toHaveBeenCalledWith(
78
69
  expect.objectContaining({
79
70
  detail: {
80
71
  nucleotideMutations: ['A123T', 'A234-'],
@@ -89,7 +80,7 @@ export const FiresFilterChangedEvents: StoryObj<MutationFilterProps> = {
89
80
  await step('Enter another valid mutation', async () => {
90
81
  await submitMutation(canvas, 'ins_123:AA');
91
82
 
92
- await expect(listenerMock).toHaveBeenCalledWith(
83
+ await expect(changedListenerMock).toHaveBeenCalledWith(
93
84
  expect.objectContaining({
94
85
  detail: {
95
86
  nucleotideMutations: ['A123T', 'A234-'],
@@ -105,7 +96,7 @@ export const FiresFilterChangedEvents: StoryObj<MutationFilterProps> = {
105
96
  const firstMutationDeleteButton = canvas.getAllByRole('button')[0];
106
97
  await waitFor(() => fireEvent.click(firstMutationDeleteButton));
107
98
 
108
- await expect(listenerMock).toHaveBeenCalledWith(
99
+ await expect(changedListenerMock).toHaveBeenCalledWith(
109
100
  expect.objectContaining({
110
101
  detail: {
111
102
  nucleotideMutations: ['A234-'],
@@ -122,18 +113,7 @@ export const FiresFilterChangedEvents: StoryObj<MutationFilterProps> = {
122
113
  export const FiresFilterOnBlurEvent: StoryObj<MutationFilterProps> = {
123
114
  ...Default,
124
115
  play: async ({ canvasElement, step }) => {
125
- const canvas = within(canvasElement);
126
-
127
- const listenerMock = fn();
128
- await step('Setup event listener mock', async () => {
129
- canvasElement.addEventListener('gs-mutation-filter-on-blur', listenerMock);
130
- });
131
-
132
- await step('wait until data is loaded', async () => {
133
- await waitFor(() => {
134
- return expect(inputField(canvas)).toBeEnabled();
135
- });
136
- });
116
+ const { canvas, onBlurListenerMock } = await prepare(canvasElement, step);
137
117
 
138
118
  await step('Move outside of input', async () => {
139
119
  await submitMutation(canvas, 'A234T');
@@ -142,7 +122,7 @@ export const FiresFilterOnBlurEvent: StoryObj<MutationFilterProps> = {
142
122
  await submitMutation(canvas, 'ins_S:123:AAA');
143
123
  await userEvent.tab();
144
124
 
145
- await expect(listenerMock).toHaveBeenCalledWith(
125
+ await expect(onBlurListenerMock).toHaveBeenCalledWith(
146
126
  expect.objectContaining({
147
127
  detail: {
148
128
  nucleotideMutations: ['A234T'],
@@ -156,9 +136,68 @@ export const FiresFilterOnBlurEvent: StoryObj<MutationFilterProps> = {
156
136
  },
157
137
  };
158
138
 
139
+ export const WithInitialValue: StoryObj<MutationFilterProps> = {
140
+ render: (args) => (
141
+ <LapisUrlContext.Provider value={LAPIS_URL}>
142
+ <ReferenceGenomeContext.Provider value={referenceGenome}>
143
+ <MutationFilter initialValue={args.initialValue} />
144
+ </ReferenceGenomeContext.Provider>
145
+ </LapisUrlContext.Provider>
146
+ ),
147
+ args: {
148
+ initialValue: {
149
+ nucleotideMutations: ['A234T'],
150
+ aminoAcidMutations: ['S:A123G'],
151
+ nucleotideInsertions: ['ins_123:AAA'],
152
+ aminoAcidInsertions: ['ins_S:123:AAA'],
153
+ },
154
+ },
155
+ play: async ({ canvasElement, step }) => {
156
+ const { canvas, onBlurListenerMock } = await prepare(canvasElement, step);
157
+
158
+ await step('Move outside of input', async () => {
159
+ await submitMutation(canvas, 'G500T');
160
+ await userEvent.tab();
161
+
162
+ await expect(onBlurListenerMock).toHaveBeenCalledWith(
163
+ expect.objectContaining({
164
+ detail: {
165
+ nucleotideMutations: ['A234T', 'G500T'],
166
+ aminoAcidMutations: ['S:A123G'],
167
+ nucleotideInsertions: ['ins_123:AAA'],
168
+ aminoAcidInsertions: ['ins_S:123:AAA'],
169
+ },
170
+ }),
171
+ );
172
+ });
173
+ },
174
+ };
175
+
176
+ async function prepare(canvasElement: HTMLElement, step: StepFunction<PreactRenderer, unknown>) {
177
+ const canvas = within(canvasElement);
178
+
179
+ const onBlurListenerMock = fn();
180
+ const changedListenerMock = fn();
181
+ await step('Setup event listener mock', async () => {
182
+ canvasElement.addEventListener('gs-mutation-filter-on-blur', onBlurListenerMock);
183
+ canvasElement.addEventListener('gs-mutation-filter-changed', changedListenerMock);
184
+ });
185
+
186
+ await step('wait until data is loaded', async () => {
187
+ await waitFor(() => {
188
+ return expect(inputField(canvas)).toBeEnabled();
189
+ });
190
+ });
191
+
192
+ return { canvas, onBlurListenerMock, changedListenerMock };
193
+ }
194
+
159
195
  const submitMutation = async (canvas: ReturnType<typeof within>, mutation: string) => {
160
196
  await userEvent.type(inputField(canvas), mutation);
161
197
  await waitFor(() => submitButton(canvas).click());
162
198
  };
163
- const inputField = (canvas: ReturnType<typeof within>) => canvas.getByPlaceholderText('Enter a mutation');
199
+
200
+ const inputField = (canvas: ReturnType<typeof within>) =>
201
+ canvas.getByPlaceholderText('Enter a mutation', { exact: false });
202
+
164
203
  const submitButton = (canvas: ReturnType<typeof within>) => canvas.getByRole('button', { name: '+' });
@@ -2,12 +2,16 @@ import { type FunctionComponent } from 'preact';
2
2
  import { useContext, useRef, useState } from 'preact/hooks';
3
3
 
4
4
  import { parseAndValidateMutation } from './parseAndValidateMutation';
5
+ import { type ReferenceGenome } from '../../lapisApi/ReferenceGenome';
5
6
  import { type Deletion, type Insertion, type Mutation, type Substitution } from '../../utils/mutations';
6
7
  import { ReferenceGenomeContext } from '../ReferenceGenomeContext';
8
+ import Info from '../components/info';
7
9
  import { singleGraphColorRGBByName } from '../shared/charts/colors';
8
10
  import { DeleteIcon } from '../shared/icons/DeleteIcon';
9
11
 
10
- export type MutationFilterProps = {};
12
+ export type MutationFilterProps = {
13
+ initialValue?: SelectedMutationFilterStrings | string[] | undefined;
14
+ };
11
15
 
12
16
  export type SelectedFilters = {
13
17
  nucleotideMutations: (Substitution | Deletion)[];
@@ -20,14 +24,11 @@ export type SelectedMutationFilterStrings = {
20
24
  [Key in keyof SelectedFilters]: string[];
21
25
  };
22
26
 
23
- export const MutationFilter: FunctionComponent<MutationFilterProps> = () => {
27
+ export const MutationFilter: FunctionComponent<MutationFilterProps> = ({ initialValue }) => {
24
28
  const referenceGenome = useContext(ReferenceGenomeContext);
25
- const [selectedFilters, setSelectedFilters] = useState<SelectedFilters>({
26
- nucleotideMutations: [],
27
- aminoAcidMutations: [],
28
- nucleotideInsertions: [],
29
- aminoAcidInsertions: [],
30
- });
29
+ const [selectedFilters, setSelectedFilters] = useState<SelectedFilters>(
30
+ getInitialState(initialValue, referenceGenome),
31
+ );
31
32
  const [inputValue, setInputValue] = useState('');
32
33
  const [isError, setIsError] = useState(false);
33
34
  const formRef = useRef<HTMLFormElement>(null);
@@ -83,11 +84,14 @@ export const MutationFilter: FunctionComponent<MutationFilterProps> = () => {
83
84
 
84
85
  return (
85
86
  <div class={`rounded-lg border border-gray-300 bg-white p-2`}>
86
- <SelectedMutationDisplay
87
- selectedFilters={selectedFilters}
88
- setSelectedFilters={setSelectedFilters}
89
- fireChangeEvent={fireChangeEvent}
90
- />
87
+ <div class='flex justify-between'>
88
+ <SelectedMutationDisplay
89
+ selectedFilters={selectedFilters}
90
+ setSelectedFilters={setSelectedFilters}
91
+ fireChangeEvent={fireChangeEvent}
92
+ />
93
+ <Info>Info for mutation filter</Info>
94
+ </div>
91
95
 
92
96
  <form className='mt-2 w-full' onSubmit={handleSubmit} ref={formRef}>
93
97
  <label className={`input flex items-center gap-2 ${isError ? 'input-error' : 'input-bordered'}`}>
@@ -96,7 +100,7 @@ export const MutationFilter: FunctionComponent<MutationFilterProps> = () => {
96
100
  type='text'
97
101
  value={inputValue}
98
102
  onInput={handleInputChange}
99
- placeholder={'Enter a mutation'}
103
+ placeholder={getPlaceholder(referenceGenome)}
100
104
  onBlur={handleOnBlur}
101
105
  />
102
106
  <button className='btn btn-sm'>+</button>
@@ -106,6 +110,50 @@ export const MutationFilter: FunctionComponent<MutationFilterProps> = () => {
106
110
  );
107
111
  };
108
112
 
113
+ function getInitialState(
114
+ initialValue: SelectedMutationFilterStrings | string[] | undefined,
115
+ referenceGenome: ReferenceGenome,
116
+ ) {
117
+ if (initialValue === undefined) {
118
+ return {
119
+ nucleotideMutations: [],
120
+ aminoAcidMutations: [],
121
+ nucleotideInsertions: [],
122
+ aminoAcidInsertions: [],
123
+ };
124
+ }
125
+
126
+ const values = Array.isArray(initialValue) ? initialValue : Object.values(initialValue).flatMap((it) => it);
127
+
128
+ return values.reduce(
129
+ (selectedFilters, value) => {
130
+ const parsedMutation = parseAndValidateMutation(value, referenceGenome);
131
+ if (parsedMutation === null) {
132
+ return selectedFilters;
133
+ }
134
+
135
+ return {
136
+ ...selectedFilters,
137
+ [parsedMutation.type]: [...selectedFilters[parsedMutation.type], parsedMutation.value],
138
+ };
139
+ },
140
+ {
141
+ nucleotideMutations: [],
142
+ aminoAcidMutations: [],
143
+ nucleotideInsertions: [],
144
+ aminoAcidInsertions: [],
145
+ } as SelectedFilters,
146
+ );
147
+ }
148
+
149
+ function getPlaceholder(referenceGenome: ReferenceGenome) {
150
+ const segmentPrefix =
151
+ referenceGenome.nucleotideSequences.length > 1 ? `${referenceGenome.nucleotideSequences[0].name}:` : '';
152
+ const firstGene = referenceGenome.genes[0].name;
153
+
154
+ return `Enter a mutation (e.g. ${segmentPrefix}A123T, ins_${segmentPrefix}123:AT, ${firstGene}:M123E, ins_${firstGene}:123:ME)`;
155
+ }
156
+
109
157
  const SelectedMutationDisplay: FunctionComponent<{
110
158
  selectedFilters: SelectedFilters;
111
159
  setSelectedFilters: (selectedFilters: SelectedFilters) => void;
@@ -5,16 +5,18 @@ import { Insertion } from '../../utils/mutations';
5
5
 
6
6
  describe('getInsertionsTableData', () => {
7
7
  test('should return the correct data', () => {
8
+ const insertion1 = new Insertion('segment1', 123, 'T');
9
+ const insertion2 = new Insertion('segment2', 234, 'AAA');
8
10
  const data = [
9
11
  {
10
12
  type: 'insertion' as const,
11
- mutation: new Insertion('segment1', 123, 'T'),
13
+ mutation: insertion1,
12
14
  count: 1,
13
15
  proportion: 0.1,
14
16
  },
15
17
  {
16
18
  type: 'insertion' as const,
17
- mutation: new Insertion('segment2', 234, 'AAA'),
19
+ mutation: insertion2,
18
20
  count: 2,
19
21
  proportion: 0.2,
20
22
  },
@@ -24,11 +26,11 @@ describe('getInsertionsTableData', () => {
24
26
 
25
27
  expect(result).toEqual([
26
28
  {
27
- insertion: 'ins_segment1:123:T',
29
+ insertion: insertion1,
28
30
  count: 1,
29
31
  },
30
32
  {
31
- insertion: 'ins_segment2:234:AAA',
33
+ insertion: insertion2,
32
34
  count: 2,
33
35
  },
34
36
  ]);
@@ -3,7 +3,7 @@ import { type InsertionEntry } from '../../types';
3
3
  export function getInsertionsTableData(data: InsertionEntry[]) {
4
4
  return data.map((mutationEntry) => {
5
5
  return {
6
- insertion: mutationEntry.mutation.toString(),
6
+ insertion: mutationEntry.mutation,
7
7
  count: mutationEntry.count,
8
8
  };
9
9
  });
@@ -4,7 +4,7 @@ import { getMutationsTableData } from './getMutationsTableData';
4
4
  import { Deletion, Substitution } from '../../utils/mutations';
5
5
 
6
6
  describe('getMutationsTableData', () => {
7
- test('should return the correct data', () => {
7
+ test('should not filter anything, when proportions are in interval', () => {
8
8
  const data = [
9
9
  {
10
10
  type: 'substitution' as const,
@@ -24,20 +24,7 @@ describe('getMutationsTableData', () => {
24
24
 
25
25
  const result = getMutationsTableData(data, proportionInterval);
26
26
 
27
- expect(result).toEqual([
28
- {
29
- mutation: 'segment1:A123T',
30
- type: 'substitution',
31
- count: 1,
32
- proportion: 0.1,
33
- },
34
- {
35
- mutation: 'segment2:C123-',
36
- type: 'deletion',
37
- count: 2,
38
- proportion: 0.2,
39
- },
40
- ]);
27
+ expect(result).toEqual(data);
41
28
  });
42
29
 
43
30
  test('should filter out data below/above proportionInterval', () => {
@@ -45,10 +32,13 @@ describe('getMutationsTableData', () => {
45
32
  const aboveInterval = 0.95;
46
33
  const inInterval = 0.5;
47
34
 
35
+ const substitutionInInterval = new Substitution('segment1', 'A', 'T', 123);
36
+ const deletionInInterval = new Deletion('segment2', 'C', 123);
37
+
48
38
  const data = [
49
39
  {
50
40
  type: 'substitution' as const,
51
- mutation: new Substitution('segment1', 'A', 'T', 123),
41
+ mutation: substitutionInInterval,
52
42
  count: 1,
53
43
  proportion: inInterval,
54
44
  },
@@ -60,7 +50,7 @@ describe('getMutationsTableData', () => {
60
50
  },
61
51
  {
62
52
  type: 'deletion' as const,
63
- mutation: new Deletion('segment2', 'C', 123),
53
+ mutation: deletionInInterval,
64
54
  count: 2,
65
55
  proportion: inInterval,
66
56
  },
@@ -78,13 +68,13 @@ describe('getMutationsTableData', () => {
78
68
 
79
69
  expect(result).toEqual([
80
70
  {
81
- mutation: 'segment1:A123T',
71
+ mutation: substitutionInInterval,
82
72
  type: 'substitution',
83
73
  count: 1,
84
74
  proportion: inInterval,
85
75
  },
86
76
  {
87
- mutation: 'segment2:C123-',
77
+ mutation: deletionInInterval,
88
78
  type: 'deletion',
89
79
  count: 2,
90
80
  proportion: inInterval,
@@ -8,7 +8,7 @@ export function getMutationsTableData(data: SubstitutionOrDeletionEntry[], propo
8
8
 
9
9
  return data.filter(byProportion).map((mutationEntry) => {
10
10
  return {
11
- mutation: mutationEntry.mutation.toString(),
11
+ mutation: mutationEntry.mutation,
12
12
  type: mutationEntry.type,
13
13
  count: mutationEntry.count,
14
14
  proportion: mutationEntry.proportion,
@@ -2,6 +2,7 @@ import { type FunctionComponent } from 'preact';
2
2
 
3
3
  import { getInsertionsTableData } from './getInsertionsTableData';
4
4
  import { type InsertionEntry } from '../../types';
5
+ import { type Insertion } from '../../utils/mutations';
5
6
  import { Table } from '../components/table';
6
7
  import { sortInsertions } from '../shared/sort/sortInsertions';
7
8
 
@@ -15,10 +16,11 @@ export const InsertionsTable: FunctionComponent<InsertionsTableProps> = ({ data
15
16
  {
16
17
  name: 'Insertion',
17
18
  sort: {
18
- compare: (a: string, b: string) => {
19
+ compare: (a: Insertion, b: Insertion) => {
19
20
  return sortInsertions(a, b);
20
21
  },
21
22
  },
23
+ formatter: (cell: Insertion) => cell.toString(),
22
24
  },
23
25
  {
24
26
  name: 'Count',
@@ -2,6 +2,7 @@ import { type FunctionComponent } from 'preact';
2
2
 
3
3
  import { getMutationsTableData } from './getMutationsTableData';
4
4
  import { type SubstitutionOrDeletionEntry } from '../../types';
5
+ import { type Deletion, type Substitution } from '../../utils/mutations';
5
6
  import type { ProportionInterval } from '../components/proportion-selector';
6
7
  import { Table } from '../components/table';
7
8
  import { sortSubstitutionsAndDeletions } from '../shared/sort/sortSubstitutionsAndDeletions';
@@ -18,10 +19,11 @@ const MutationsTable: FunctionComponent<MutationsTableProps> = ({ data, proporti
18
19
  {
19
20
  name: 'Mutation',
20
21
  sort: {
21
- compare: (a: string, b: string) => {
22
+ compare: (a: Substitution | Deletion, b: Substitution | Deletion) => {
22
23
  return sortSubstitutionsAndDeletions(a, b);
23
24
  },
24
25
  },
26
+ formatter: (cell: Substitution | Deletion) => cell.toString(),
25
27
  },
26
28
  {
27
29
  name: 'Type',
@@ -22,6 +22,8 @@ const meta: Meta<MutationsProps> = {
22
22
  options: ['table', 'grid', 'insertions'],
23
23
  control: { type: 'check' },
24
24
  },
25
+ size: [{ control: 'object' }],
26
+ headline: { control: 'text' },
25
27
  },
26
28
  };
27
29
 
@@ -31,7 +33,13 @@ const Template = {
31
33
  render: (args: MutationsProps) => (
32
34
  <LapisUrlContext.Provider value={LAPIS_URL}>
33
35
  <ReferenceGenomeContext.Provider value={referenceGenome}>
34
- <Mutations variant={args.variant} sequenceType={args.sequenceType} views={args.views} />
36
+ <Mutations
37
+ variant={args.variant}
38
+ sequenceType={args.sequenceType}
39
+ views={args.views}
40
+ size={args.size}
41
+ headline={args.headline}
42
+ />
35
43
  </ReferenceGenomeContext.Provider>
36
44
  </LapisUrlContext.Provider>
37
45
  ),
@@ -43,6 +51,8 @@ export const Default: StoryObj<MutationsProps> = {
43
51
  variant: { country: 'Switzerland', pangoLineage: 'B.1.1.7', dateTo: '2022-01-01' },
44
52
  sequenceType: 'nucleotide',
45
53
  views: ['grid', 'table', 'insertions'],
54
+ size: { width: '100%', height: '700px' },
55
+ headline: 'Mutations',
46
56
  },
47
57
  parameters: {
48
58
  fetchMock: {
@@ -24,6 +24,7 @@ import { type DisplayedMutationType, MutationTypeSelector } from '../components/
24
24
  import { NoDataDisplay } from '../components/no-data-display';
25
25
  import type { ProportionInterval } from '../components/proportion-selector';
26
26
  import { ProportionSelectorDropdown } from '../components/proportion-selector-dropdown';
27
+ import { ResizeContainer, type Size } from '../components/resize-container';
27
28
  import Tabs from '../components/tabs';
28
29
  import { useQuery } from '../useQuery';
29
30
 
@@ -33,15 +34,22 @@ export interface MutationsProps {
33
34
  variant: LapisFilter;
34
35
  sequenceType: SequenceType;
35
36
  views: View[];
37
+ size?: Size;
38
+ headline?: string;
36
39
  }
37
40
 
38
- export const Mutations: FunctionComponent<MutationsProps> = ({ variant, sequenceType, views }) => {
41
+ export const Mutations: FunctionComponent<MutationsProps> = ({
42
+ variant,
43
+ sequenceType,
44
+ views,
45
+ size,
46
+ headline = 'Mutations',
47
+ }) => {
39
48
  const lapis = useContext(LapisUrlContext);
40
49
  const { data, error, isLoading } = useQuery(async () => {
41
50
  return queryMutationsData(variant, sequenceType, lapis);
42
51
  }, [variant, sequenceType, lapis]);
43
52
 
44
- const headline = 'Mutations';
45
53
  if (isLoading) {
46
54
  return (
47
55
  <Headline heading={headline}>
@@ -67,9 +75,11 @@ export const Mutations: FunctionComponent<MutationsProps> = ({ variant, sequence
67
75
  }
68
76
 
69
77
  return (
70
- <Headline heading={headline}>
71
- <MutationsTabs mutationsData={data} sequenceType={sequenceType} views={views} />
72
- </Headline>
78
+ <ResizeContainer size={size} defaultSize={{ height: '700px', width: '100%' }}>
79
+ <Headline heading={headline}>
80
+ <MutationsTabs mutationsData={data} sequenceType={sequenceType} views={views} />
81
+ </Headline>
82
+ </ResizeContainer>
73
83
  );
74
84
  };
75
85
 
@@ -182,11 +192,18 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
182
192
  {activeTab === 'Insertions' && (
183
193
  <CsvDownloadButton
184
194
  className='mx-1 btn btn-xs'
185
- getData={() => getInsertionsTableData(filteredData.insertions)}
195
+ getData={() =>
196
+ getInsertionsTableData(filteredData.insertions).map((row) => {
197
+ return {
198
+ insertion: row.insertion.toString(),
199
+ count: row.count,
200
+ };
201
+ })
202
+ }
186
203
  filename='insertions.csv'
187
204
  />
188
205
  )}
189
- <Info className='mx-1' content='Info for mutations' />
206
+ <Info>Info for mutations</Info>
190
207
  </div>
191
208
  );
192
209
  };
@@ -28,6 +28,7 @@ const PrevalenceOverTimeBarChart = ({
28
28
  datasets: data.map((graphData, index) => datasets(graphData, index, confidenceIntervalMethod)),
29
29
  },
30
30
  options: {
31
+ maintainAspectRatio: false,
31
32
  animation: false,
32
33
  scales: {
33
34
  y: getYAxisScale(yAxisScaleType),
@@ -43,6 +43,7 @@ const PrevalenceOverTimeBubbleChart = ({ data, yAxisScaleType }: PrevalenceOverT
43
43
  },
44
44
  options: {
45
45
  animation: false,
46
+ maintainAspectRatio: false,
46
47
  scales: {
47
48
  x: {
48
49
  ticks: {
@@ -36,6 +36,7 @@ const PrevalenceOverTimeLineChart = ({
36
36
  },
37
37
  options: {
38
38
  animation: false,
39
+ maintainAspectRatio: false,
39
40
  scales: {
40
41
  y: getYAxisScale(yAxisScaleType),
41
42
  },
@@ -29,6 +29,8 @@ export default {
29
29
  options: ['wilson'],
30
30
  control: { type: 'check' },
31
31
  },
32
+ size: [{ control: 'object' }],
33
+ headline: { control: 'text' },
32
34
  },
33
35
  };
34
36
 
@@ -42,6 +44,8 @@ const Template = {
42
44
  smoothingWindow={args.smoothingWindow}
43
45
  views={args.views}
44
46
  confidenceIntervalMethods={args.confidenceIntervalMethods}
47
+ size={args.size}
48
+ headline={args.headline}
45
49
  />
46
50
  </LapisUrlContext.Provider>
47
51
  ),
@@ -59,6 +63,8 @@ export const TwoVariants = {
59
63
  smoothingWindow: 0,
60
64
  views: ['bar', 'line', 'bubble', 'table'],
61
65
  confidenceIntervalMethods: ['wilson'],
66
+ size: { width: '100%', height: '700px' },
67
+ headline: 'Prevalence over time',
62
68
  },
63
69
  parameters: {
64
70
  fetchMock: {
@@ -124,6 +130,8 @@ export const OneVariant = {
124
130
  smoothingWindow: 7,
125
131
  views: ['bar', 'line', 'bubble', 'table'],
126
132
  confidenceIntervalMethods: ['wilson'],
133
+ size: { width: '100%', height: '700px' },
134
+ headline: 'Prevalence over time',
127
135
  },
128
136
  parameters: {
129
137
  fetchMock: {