@genspectrum/dashboard-components 0.16.3 → 0.16.4

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 (25) hide show
  1. package/dist/assets/{mutationOverTimeWorker-DJcZmEH9.js.map → mutationOverTimeWorker-CPfQDLe6.js.map} +1 -1
  2. package/dist/components.d.ts +30 -29
  3. package/dist/components.js +833 -794
  4. package/dist/components.js.map +1 -1
  5. package/dist/style.css +5 -0
  6. package/dist/util.d.ts +77 -33
  7. package/package.json +2 -1
  8. package/src/preact/components/annotated-mutation.stories.tsx +2 -1
  9. package/src/preact/components/annotated-mutation.tsx +6 -2
  10. package/src/preact/mutationComparison/mutation-comparison-table.tsx +14 -1
  11. package/src/preact/mutationComparison/mutation-comparison-venn.tsx +39 -8
  12. package/src/preact/mutationComparison/mutation-comparison.stories.tsx +36 -12
  13. package/src/preact/mutationComparison/mutation-comparison.tsx +2 -0
  14. package/src/preact/mutations/mutations.stories.tsx +3 -9
  15. package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +3 -8
  16. package/src/preact/shared/stories/expectMutationAnnotation.ts +13 -0
  17. package/src/utilEntrypoint.ts +2 -0
  18. package/src/web-components/visualization/gs-mutation-comparison.stories.ts +18 -1
  19. package/src/web-components/visualization/gs-mutation-comparison.tsx +19 -8
  20. package/src/web-components/visualization/gs-mutations-over-time.stories.ts +2 -1
  21. package/src/web-components/visualization/gs-mutations.stories.ts +2 -1
  22. package/standalone-bundle/assets/mutationOverTimeWorker-CERZSdcA.js.map +1 -1
  23. package/standalone-bundle/dashboard-components.js +12787 -12215
  24. package/standalone-bundle/dashboard-components.js.map +1 -1
  25. package/standalone-bundle/style.css +1 -1
package/dist/style.css CHANGED
@@ -3292,6 +3292,11 @@ input.tab:checked + .tab-content,
3292
3292
  .cursor-pointer {
3293
3293
  cursor: pointer;
3294
3294
  }
3295
+ .select-text {
3296
+ -webkit-user-select: text;
3297
+ -moz-user-select: text;
3298
+ user-select: text;
3299
+ }
3295
3300
  .list-inside {
3296
3301
  list-style-position: inside;
3297
3302
  }
package/dist/util.d.ts CHANGED
@@ -202,6 +202,50 @@ declare const mapSourceSchema: default_2.ZodObject<{
202
202
  topologyObjectsKey: string;
203
203
  }>;
204
204
 
205
+ export declare type MutationAnnotation = default_2.infer<typeof mutationAnnotationSchema>;
206
+
207
+ export declare type MutationAnnotations = default_2.infer<typeof mutationAnnotationsSchema>;
208
+
209
+ declare const mutationAnnotationSchema: default_2.ZodObject<{
210
+ name: default_2.ZodString;
211
+ description: default_2.ZodString;
212
+ symbol: default_2.ZodString;
213
+ nucleotideMutations: default_2.ZodArray<default_2.ZodString, "many">;
214
+ aminoAcidMutations: default_2.ZodArray<default_2.ZodString, "many">;
215
+ }, "strip", default_2.ZodTypeAny, {
216
+ symbol: string;
217
+ name: string;
218
+ description: string;
219
+ nucleotideMutations: string[];
220
+ aminoAcidMutations: string[];
221
+ }, {
222
+ symbol: string;
223
+ name: string;
224
+ description: string;
225
+ nucleotideMutations: string[];
226
+ aminoAcidMutations: string[];
227
+ }>;
228
+
229
+ declare const mutationAnnotationsSchema: default_2.ZodArray<default_2.ZodObject<{
230
+ name: default_2.ZodString;
231
+ description: default_2.ZodString;
232
+ symbol: default_2.ZodString;
233
+ nucleotideMutations: default_2.ZodArray<default_2.ZodString, "many">;
234
+ aminoAcidMutations: default_2.ZodArray<default_2.ZodString, "many">;
235
+ }, "strip", default_2.ZodTypeAny, {
236
+ symbol: string;
237
+ name: string;
238
+ description: string;
239
+ nucleotideMutations: string[];
240
+ aminoAcidMutations: string[];
241
+ }, {
242
+ symbol: string;
243
+ name: string;
244
+ description: string;
245
+ nucleotideMutations: string[];
246
+ aminoAcidMutations: string[];
247
+ }>, "many">;
248
+
205
249
  export declare type MutationComparisonProps = default_2.infer<typeof mutationComparisonPropsSchema>;
206
250
 
207
251
  declare const mutationComparisonPropsSchema: default_2.ZodObject<{
@@ -247,6 +291,7 @@ declare const mutationComparisonPropsSchema: default_2.ZodObject<{
247
291
  pageSize: default_2.ZodUnion<[default_2.ZodBoolean, default_2.ZodNumber]>;
248
292
  }, "strip", default_2.ZodTypeAny, {
249
293
  width: string;
294
+ sequenceType: "nucleotide" | "amino acid";
250
295
  pageSize: number | boolean;
251
296
  lapisFilters: {
252
297
  lapisFilter: Record<string, string | number | boolean | string[] | null | undefined> & {
@@ -257,11 +302,11 @@ declare const mutationComparisonPropsSchema: default_2.ZodObject<{
257
302
  };
258
303
  displayName: string;
259
304
  }[];
260
- sequenceType: "nucleotide" | "amino acid";
261
305
  views: ("table" | "venn")[];
262
306
  height?: string | undefined;
263
307
  }, {
264
308
  width: string;
309
+ sequenceType: "nucleotide" | "amino acid";
265
310
  pageSize: number | boolean;
266
311
  lapisFilters: {
267
312
  lapisFilter: Record<string, string | number | boolean | string[] | null | undefined> & {
@@ -272,7 +317,6 @@ declare const mutationComparisonPropsSchema: default_2.ZodObject<{
272
317
  };
273
318
  displayName: string;
274
319
  }[];
275
- sequenceType: "nucleotide" | "amino acid";
276
320
  views: ("table" | "venn")[];
277
321
  height?: string | undefined;
278
322
  }>;
@@ -348,8 +392,8 @@ declare const mutationsPropsSchema: default_2.ZodObject<{
348
392
  aminoAcidInsertions?: string[] | undefined;
349
393
  };
350
394
  width: string;
351
- pageSize: number | boolean;
352
395
  sequenceType: "nucleotide" | "amino acid";
396
+ pageSize: number | boolean;
353
397
  views: ("table" | "grid" | "insertions")[];
354
398
  height?: string | undefined;
355
399
  baselineLapisFilter?: (Record<string, string | number | boolean | string[] | null | undefined> & {
@@ -366,8 +410,8 @@ declare const mutationsPropsSchema: default_2.ZodObject<{
366
410
  aminoAcidInsertions?: string[] | undefined;
367
411
  };
368
412
  width: string;
369
- pageSize: number | boolean;
370
413
  sequenceType: "nucleotide" | "amino acid";
414
+ pageSize: number | boolean;
371
415
  views: ("table" | "grid" | "insertions")[];
372
416
  height?: string | undefined;
373
417
  baselineLapisFilter?: (Record<string, string | number | boolean | string[] | null | undefined> & {
@@ -880,7 +924,7 @@ declare global {
880
924
 
881
925
  declare global {
882
926
  interface HTMLElementTagNameMap {
883
- 'gs-relative-growth-advantage': RelativeGrowthAdvantageComponent;
927
+ 'gs-prevalence-over-time': PrevalenceOverTimeComponent;
884
928
  }
885
929
  }
886
930
 
@@ -888,7 +932,7 @@ declare global {
888
932
  declare global {
889
933
  namespace JSX {
890
934
  interface IntrinsicElements {
891
- 'gs-relative-growth-advantage': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
935
+ 'gs-prevalence-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
892
936
  }
893
937
  }
894
938
  }
@@ -896,7 +940,7 @@ declare global {
896
940
 
897
941
  declare global {
898
942
  interface HTMLElementTagNameMap {
899
- 'gs-prevalence-over-time': PrevalenceOverTimeComponent;
943
+ 'gs-relative-growth-advantage': RelativeGrowthAdvantageComponent;
900
944
  }
901
945
  }
902
946
 
@@ -904,7 +948,7 @@ declare global {
904
948
  declare global {
905
949
  namespace JSX {
906
950
  interface IntrinsicElements {
907
- 'gs-prevalence-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
951
+ 'gs-relative-growth-advantage': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
908
952
  }
909
953
  }
910
954
  }
@@ -912,7 +956,7 @@ declare global {
912
956
 
913
957
  declare global {
914
958
  interface HTMLElementTagNameMap {
915
- 'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
959
+ 'gs-aggregate': AggregateComponent;
916
960
  }
917
961
  }
918
962
 
@@ -920,7 +964,7 @@ declare global {
920
964
  declare global {
921
965
  namespace JSX {
922
966
  interface IntrinsicElements {
923
- 'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
967
+ 'gs-aggregate': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
924
968
  }
925
969
  }
926
970
  }
@@ -928,7 +972,7 @@ declare global {
928
972
 
929
973
  declare global {
930
974
  interface HTMLElementTagNameMap {
931
- 'gs-mutations-over-time': MutationsOverTimeComponent;
975
+ 'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
932
976
  }
933
977
  }
934
978
 
@@ -936,7 +980,7 @@ declare global {
936
980
  declare global {
937
981
  namespace JSX {
938
982
  interface IntrinsicElements {
939
- 'gs-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
983
+ 'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
940
984
  }
941
985
  }
942
986
  }
@@ -944,7 +988,7 @@ declare global {
944
988
 
945
989
  declare global {
946
990
  interface HTMLElementTagNameMap {
947
- 'gs-aggregate': AggregateComponent;
991
+ 'gs-mutations-over-time': MutationsOverTimeComponent;
948
992
  }
949
993
  }
950
994
 
@@ -952,7 +996,7 @@ declare global {
952
996
  declare global {
953
997
  namespace JSX {
954
998
  interface IntrinsicElements {
955
- 'gs-aggregate': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
999
+ 'gs-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
956
1000
  }
957
1001
  }
958
1002
  }
@@ -960,7 +1004,7 @@ declare global {
960
1004
 
961
1005
  declare global {
962
1006
  interface HTMLElementTagNameMap {
963
- 'gs-statistics': StatisticsComponent;
1007
+ 'gs-sequences-by-location': SequencesByLocationComponent;
964
1008
  }
965
1009
  }
966
1010
 
@@ -968,7 +1012,7 @@ declare global {
968
1012
  declare global {
969
1013
  namespace JSX {
970
1014
  interface IntrinsicElements {
971
- 'gs-statistics': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1015
+ 'gs-sequences-by-location': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
972
1016
  }
973
1017
  }
974
1018
  }
@@ -976,10 +1020,7 @@ declare global {
976
1020
 
977
1021
  declare global {
978
1022
  interface HTMLElementTagNameMap {
979
- 'gs-location-filter': LocationFilterComponent;
980
- }
981
- interface HTMLElementEventMap {
982
- 'gs-location-changed': LocationChangedEvent;
1023
+ 'gs-statistics': StatisticsComponent;
983
1024
  }
984
1025
  }
985
1026
 
@@ -987,7 +1028,7 @@ declare global {
987
1028
  declare global {
988
1029
  namespace JSX {
989
1030
  interface IntrinsicElements {
990
- 'gs-location-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1031
+ 'gs-statistics': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
991
1032
  }
992
1033
  }
993
1034
  }
@@ -995,7 +1036,11 @@ declare global {
995
1036
 
996
1037
  declare global {
997
1038
  interface HTMLElementTagNameMap {
998
- 'gs-sequences-by-location': SequencesByLocationComponent;
1039
+ 'gs-date-range-filter': DateRangeFilterComponent;
1040
+ }
1041
+ interface HTMLElementEventMap {
1042
+ 'gs-date-range-filter-changed': CustomEvent<Record<string, string>>;
1043
+ 'gs-date-range-option-changed': DateRangeOptionChangedEvent;
999
1044
  }
1000
1045
  }
1001
1046
 
@@ -1003,7 +1048,7 @@ declare global {
1003
1048
  declare global {
1004
1049
  namespace JSX {
1005
1050
  interface IntrinsicElements {
1006
- 'gs-sequences-by-location': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1051
+ 'gs-date-range-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1007
1052
  }
1008
1053
  }
1009
1054
  }
@@ -1027,11 +1072,10 @@ declare global {
1027
1072
 
1028
1073
  declare global {
1029
1074
  interface HTMLElementTagNameMap {
1030
- 'gs-date-range-filter': DateRangeFilterComponent;
1075
+ 'gs-location-filter': LocationFilterComponent;
1031
1076
  }
1032
1077
  interface HTMLElementEventMap {
1033
- 'gs-date-range-filter-changed': CustomEvent<Record<string, string>>;
1034
- 'gs-date-range-option-changed': DateRangeOptionChangedEvent;
1078
+ 'gs-location-changed': LocationChangedEvent;
1035
1079
  }
1036
1080
  }
1037
1081
 
@@ -1039,7 +1083,7 @@ declare global {
1039
1083
  declare global {
1040
1084
  namespace JSX {
1041
1085
  interface IntrinsicElements {
1042
- 'gs-date-range-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1086
+ 'gs-location-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1043
1087
  }
1044
1088
  }
1045
1089
  }
@@ -1066,10 +1110,10 @@ declare global {
1066
1110
 
1067
1111
  declare global {
1068
1112
  interface HTMLElementTagNameMap {
1069
- 'gs-mutation-filter': MutationFilterComponent;
1113
+ 'gs-lineage-filter': LineageFilterComponent;
1070
1114
  }
1071
1115
  interface HTMLElementEventMap {
1072
- 'gs-mutation-filter-changed': CustomEvent<MutationsFilter>;
1116
+ 'gs-lineage-filter-changed': LineageFilterChangedEvent;
1073
1117
  }
1074
1118
  }
1075
1119
 
@@ -1077,7 +1121,7 @@ declare global {
1077
1121
  declare global {
1078
1122
  namespace JSX {
1079
1123
  interface IntrinsicElements {
1080
- 'gs-mutation-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1124
+ 'gs-lineage-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1081
1125
  }
1082
1126
  }
1083
1127
  }
@@ -1085,10 +1129,10 @@ declare global {
1085
1129
 
1086
1130
  declare global {
1087
1131
  interface HTMLElementTagNameMap {
1088
- 'gs-lineage-filter': LineageFilterComponent;
1132
+ 'gs-mutation-filter': MutationFilterComponent;
1089
1133
  }
1090
1134
  interface HTMLElementEventMap {
1091
- 'gs-lineage-filter-changed': LineageFilterChangedEvent;
1135
+ 'gs-mutation-filter-changed': CustomEvent<MutationsFilter>;
1092
1136
  }
1093
1137
  }
1094
1138
 
@@ -1096,7 +1140,7 @@ declare global {
1096
1140
  declare global {
1097
1141
  namespace JSX {
1098
1142
  interface IntrinsicElements {
1099
- 'gs-lineage-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1143
+ 'gs-mutation-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1100
1144
  }
1101
1145
  }
1102
1146
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genspectrum/dashboard-components",
3
- "version": "0.16.3",
3
+ "version": "0.16.4",
4
4
  "description": "GenSpectrum web components for building dashboards",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-only",
@@ -87,6 +87,7 @@
87
87
  "chartjs-chart-error-bars": "^4.4.0",
88
88
  "chartjs-chart-venn": "^4.3.0",
89
89
  "dayjs": "^1.11.10",
90
+ "dompurify": "^3.2.4",
90
91
  "downshift": "^9.0.8",
91
92
  "flatpickr": "^4.6.13",
92
93
  "gridjs": "^6.2.0",
@@ -66,7 +66,7 @@ export const MutationWithAnnotationEntry: StoryObj<AnnotatedMutationProps & { an
66
66
  annotations: [
67
67
  {
68
68
  name: 'Test annotation',
69
- description: 'This is a test annotation',
69
+ description: 'This is a test annotation <a class="link" href="/">with a link.</a>',
70
70
  symbol: '*',
71
71
  nucleotideMutations: ['A23403G'],
72
72
  aminoAcidMutations: [],
@@ -81,6 +81,7 @@ export const MutationWithAnnotationEntry: StoryObj<AnnotatedMutationProps & { an
81
81
 
82
82
  await userEvent.click(canvas.getByText('A23403G'));
83
83
  await waitFor(() => expect(getAnnotationName(canvas)).toBeVisible());
84
+ await expect(canvas.getByRole('link', { name: 'with a link.' })).toBeVisible();
84
85
  },
85
86
  };
86
87
 
@@ -1,3 +1,4 @@
1
+ import DOMPurify from 'dompurify';
1
2
  import { useRef } from 'gridjs';
2
3
  import { Fragment, type FunctionComponent, type RefObject } from 'preact';
3
4
 
@@ -56,14 +57,17 @@ const AnnotatedMutationWithoutContext: FunctionComponent<AnnotatedMutationWithou
56
57
  {mutationAnnotations.map((annotation) => (
57
58
  <Fragment key={annotation.name}>
58
59
  <InfoHeadline2>{annotation.name}</InfoHeadline2>
59
- <InfoParagraph>{annotation.description}</InfoParagraph>
60
+ <InfoParagraph>
61
+ {/* eslint-disable-next-line react/no-danger */}
62
+ <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(annotation.description) }} />
63
+ </InfoParagraph>
60
64
  </Fragment>
61
65
  ))}
62
66
  </div>
63
67
  );
64
68
 
65
69
  return (
66
- <ButtonWithModalDialog modalContent={modalContent} modalRef={modalRef}>
70
+ <ButtonWithModalDialog buttonClassName={'select-text'} modalContent={modalContent} modalRef={modalRef}>
67
71
  {mutation.code}
68
72
  <sup>
69
73
  {mutationAnnotations
@@ -3,7 +3,10 @@ import { type FunctionComponent } from 'preact';
3
3
  import { getMutationComparisonTableData } from './getMutationComparisonTableData';
4
4
  import { type MutationData } from './queryMutationData';
5
5
  import { type Dataset } from '../../operator/Dataset';
6
+ import type { SequenceType } from '../../types';
6
7
  import { type DeletionClass, type SubstitutionClass } from '../../utils/mutations';
8
+ import { useMutationAnnotationsProvider } from '../MutationAnnotationsContext';
9
+ import { GridJsAnnotatedMutation } from '../components/annotated-mutation';
7
10
  import { type ProportionInterval } from '../components/proportion-selector';
8
11
  import { Table } from '../components/table';
9
12
  import { sortSubstitutionsAndDeletions } from '../shared/sort/sortSubstitutionsAndDeletions';
@@ -13,20 +16,30 @@ export interface MutationsTableProps {
13
16
  data: Dataset<MutationData>;
14
17
  proportionInterval: ProportionInterval;
15
18
  pageSize: boolean | number;
19
+ sequenceType: SequenceType;
16
20
  }
17
21
 
18
22
  export const MutationComparisonTable: FunctionComponent<MutationsTableProps> = ({
19
23
  data,
20
24
  proportionInterval,
21
25
  pageSize,
26
+ sequenceType,
22
27
  }) => {
28
+ const annotationsProvider = useMutationAnnotationsProvider();
29
+
23
30
  const headers = [
24
31
  {
25
32
  name: 'Mutation',
26
33
  sort: {
27
34
  compare: sortSubstitutionsAndDeletions,
28
35
  },
29
- formatter: (cell: SubstitutionClass | DeletionClass) => cell.toString(),
36
+ formatter: (cell: SubstitutionClass | DeletionClass) => (
37
+ <GridJsAnnotatedMutation
38
+ mutation={cell}
39
+ sequenceType={sequenceType}
40
+ annotationsProvider={annotationsProvider}
41
+ />
42
+ ),
30
43
  },
31
44
  {
32
45
  name: 'Prevalence',
@@ -1,10 +1,13 @@
1
1
  import { type ActiveElement, Chart, type ChartConfiguration, type ChartEvent, registerables } from 'chart.js';
2
2
  import { ArcSlice, extractSets, VennDiagramController } from 'chartjs-chart-venn';
3
- import { type FunctionComponent } from 'preact';
3
+ import { Fragment, type FunctionComponent } from 'preact';
4
4
  import { useMemo, useState } from 'preact/hooks';
5
5
 
6
6
  import { type MutationData } from './queryMutationData';
7
7
  import { type Dataset } from '../../operator/Dataset';
8
+ import { type SequenceType } from '../../types';
9
+ import { DeletionClass, SubstitutionClass } from '../../utils/mutations';
10
+ import { AnnotatedMutation } from '../components/annotated-mutation';
8
11
  import GsChart from '../components/chart';
9
12
  import { type ProportionInterval } from '../components/proportion-selector';
10
13
 
@@ -14,12 +17,14 @@ export interface MutationComparisonVennProps {
14
17
  data: Dataset<MutationData>;
15
18
  proportionInterval: ProportionInterval;
16
19
  maintainAspectRatio: boolean;
20
+ sequenceType: SequenceType;
17
21
  }
18
22
 
19
23
  export const MutationComparisonVenn: FunctionComponent<MutationComparisonVennProps> = ({
20
24
  data,
21
25
  proportionInterval,
22
26
  maintainAspectRatio,
27
+ sequenceType,
23
28
  }) => {
24
29
  const [selectedDatasetIndex, setSelectedDatasetIndex] = useState<null | number>(null);
25
30
 
@@ -105,22 +110,48 @@ export const MutationComparisonVenn: FunctionComponent<MutationComparisonVennPro
105
110
  <div className='flex-1'>
106
111
  <GsChart configuration={config} />
107
112
  </div>
108
- <p class='flex flex-wrap break-words m-2'>{getSelectedMutationsDescription(selectedDatasetIndex, sets)}</p>
113
+ <p class='flex flex-wrap break-words m-2'>
114
+ <SelectedMutationsDescription
115
+ selectedDatasetIndex={selectedDatasetIndex}
116
+ sets={sets}
117
+ sequenceType={sequenceType}
118
+ />
119
+ </p>
109
120
  </div>
110
121
  );
111
122
  };
112
123
 
113
124
  const noElementSelectedMessage = 'You have no elements selected. Click in the venn diagram to select.';
114
125
 
115
- function getSelectedMutationsDescription(
116
- selectedDatasetIndex: number | null,
117
- sets: ReturnType<typeof extractSets<string>>,
118
- ) {
126
+ type SelectedMutationsDescriptionProps = {
127
+ selectedDatasetIndex: number | null;
128
+ sets: ReturnType<typeof extractSets<string>>;
129
+ sequenceType: SequenceType;
130
+ };
131
+
132
+ const SelectedMutationsDescription: FunctionComponent<SelectedMutationsDescriptionProps> = ({
133
+ selectedDatasetIndex,
134
+ sets,
135
+ sequenceType,
136
+ }) => {
119
137
  if (selectedDatasetIndex === null) {
120
138
  return noElementSelectedMessage;
121
139
  }
122
140
 
123
141
  const values = sets.datasets[0].data[selectedDatasetIndex].values;
124
142
  const label = sets.datasets[0].data[selectedDatasetIndex].label;
125
- return `${label}: ${values.join(', ')}` || '';
126
- }
143
+ return (
144
+ <span>
145
+ {`${label}: `}
146
+ {values
147
+ .map((value) => SubstitutionClass.parse(value) ?? DeletionClass.parse(value))
148
+ .filter((value) => value !== null)
149
+ .map((value, index) => (
150
+ <Fragment key={value}>
151
+ {index > 0 && ', '}
152
+ <AnnotatedMutation mutation={value} sequenceType={sequenceType} />
153
+ </Fragment>
154
+ ))}
155
+ </span>
156
+ );
157
+ };
@@ -7,7 +7,9 @@ import { MutationComparison, type MutationComparisonProps } from './mutation-com
7
7
  import { LAPIS_URL, NUCLEOTIDE_MUTATIONS_ENDPOINT } from '../../constants';
8
8
  import referenceGenome from '../../lapisApi/__mockData__/referenceGenome.json';
9
9
  import { LapisUrlContextProvider } from '../LapisUrlContext';
10
+ import { MutationAnnotationsContextProvider } from '../MutationAnnotationsContext';
10
11
  import { ReferenceGenomeContext } from '../ReferenceGenomeContext';
12
+ import { expectMutationAnnotation } from '../shared/stories/expectMutationAnnotation';
11
13
 
12
14
  const dateToSomeDataset = '2022-01-01';
13
15
 
@@ -74,20 +76,39 @@ const meta: Meta<MutationComparisonProps> = {
74
76
 
75
77
  export default meta;
76
78
 
79
+ const mutationAnnotations = [
80
+ {
81
+ name: 'I am a mutation annotation!',
82
+ description: 'This describes what is special about these mutations.',
83
+ symbol: '#',
84
+ nucleotideMutations: ['G199-', 'C3037T'],
85
+ aminoAcidMutations: ['N:G204R'],
86
+ },
87
+ {
88
+ name: 'I am another mutation annotation!',
89
+ description: 'This describes what is special about these other mutations.',
90
+ symbol: '+',
91
+ nucleotideMutations: ['C3037T', 'A23403G'],
92
+ aminoAcidMutations: ['ORF1a:I2230T'],
93
+ },
94
+ ];
95
+
77
96
  const Template: StoryObj<MutationComparisonProps> = {
78
97
  render: (args) => (
79
- <LapisUrlContextProvider value={LAPIS_URL}>
80
- <ReferenceGenomeContext.Provider value={referenceGenome}>
81
- <MutationComparison
82
- lapisFilters={args.lapisFilters}
83
- sequenceType={args.sequenceType}
84
- views={args.views}
85
- width={args.width}
86
- height={args.height}
87
- pageSize={args.pageSize}
88
- />
89
- </ReferenceGenomeContext.Provider>
90
- </LapisUrlContextProvider>
98
+ <MutationAnnotationsContextProvider value={mutationAnnotations}>
99
+ <LapisUrlContextProvider value={LAPIS_URL}>
100
+ <ReferenceGenomeContext.Provider value={referenceGenome}>
101
+ <MutationComparison
102
+ lapisFilters={args.lapisFilters}
103
+ sequenceType={args.sequenceType}
104
+ views={args.views}
105
+ width={args.width}
106
+ height={args.height}
107
+ pageSize={args.pageSize}
108
+ />
109
+ </ReferenceGenomeContext.Provider>
110
+ </LapisUrlContextProvider>
111
+ </MutationAnnotationsContextProvider>
91
112
  ),
92
113
  };
93
114
 
@@ -114,6 +135,9 @@ export const TwoVariants: StoryObj<MutationComparisonProps> = {
114
135
  width: '100%',
115
136
  pageSize: 10,
116
137
  },
138
+ play: async ({ canvasElement }) => {
139
+ await expectMutationAnnotation(canvasElement, 'C3037T');
140
+ },
117
141
  };
118
142
 
119
143
  export const FilterForOnlyDeletions: StoryObj<MutationComparisonProps> = {
@@ -104,6 +104,7 @@ const MutationComparisonTabs: FunctionComponent<MutationComparisonTabsProps> = (
104
104
  data={{ content: filteredData }}
105
105
  proportionInterval={proportionInterval}
106
106
  pageSize={originalComponentProps.pageSize}
107
+ sequenceType={originalComponentProps.sequenceType}
107
108
  />
108
109
  ),
109
110
  };
@@ -115,6 +116,7 @@ const MutationComparisonTabs: FunctionComponent<MutationComparisonTabsProps> = (
115
116
  data={{ content: filteredData }}
116
117
  proportionInterval={proportionInterval}
117
118
  maintainAspectRatio={maintainAspectRatio}
119
+ sequenceType={originalComponentProps.sequenceType}
118
120
  />
119
121
  ),
120
122
  };
@@ -1,5 +1,5 @@
1
1
  import { type Meta, type StoryObj } from '@storybook/preact';
2
- import { expect, userEvent, waitFor, within } from '@storybook/test';
2
+ import { expect, waitFor, within } from '@storybook/test';
3
3
 
4
4
  import nucleotideInsertions from './__mockData__/nucleotideInsertions.json';
5
5
  import nucleotideMutations from './__mockData__/nucleotideMutations.json';
@@ -16,6 +16,7 @@ import overallVariantCount from '../../preact/mutations/__mockData__/overallVari
16
16
  import { LapisUrlContextProvider } from '../LapisUrlContext';
17
17
  import { MutationAnnotationsContextProvider } from '../MutationAnnotationsContext';
18
18
  import { ReferenceGenomeContext } from '../ReferenceGenomeContext';
19
+ import { expectMutationAnnotation } from '../shared/stories/expectMutationAnnotation';
19
20
 
20
21
  const meta: Meta<MutationsProps> = {
21
22
  title: 'Visualization/Mutations',
@@ -165,13 +166,6 @@ export const TableTab: StoryObj<MutationsProps> = {
165
166
  views: ['table'],
166
167
  },
167
168
  play: async ({ canvasElement }) => {
168
- const canvas = within(canvasElement);
169
-
170
- await waitFor(async () => {
171
- const annotatedMutation = canvas.getByText('C241T');
172
- await expect(annotatedMutation).toBeVisible();
173
- await userEvent.click(annotatedMutation);
174
- });
175
- await waitFor(() => expect(canvas.getByText('Annotations for C241T')).toBeVisible());
169
+ await expectMutationAnnotation(canvasElement, 'C241T');
176
170
  },
177
171
  };
@@ -8,6 +8,7 @@ import { LapisUrlContextProvider } from '../LapisUrlContext';
8
8
  import { MutationAnnotationsContextProvider } from '../MutationAnnotationsContext';
9
9
  import { ReferenceGenomeContext } from '../ReferenceGenomeContext';
10
10
  import { expectInvalidAttributesErrorMessage } from '../shared/stories/expectErrorMessage';
11
+ import { expectMutationAnnotation } from '../shared/stories/expectMutationAnnotation';
11
12
 
12
13
  const meta: Meta<MutationsOverTimeProps> = {
13
14
  title: 'Visualization/Mutation over time',
@@ -75,14 +76,8 @@ export const Default: StoryObj<MutationsOverTimeProps> = {
75
76
  lapisDateField: 'date',
76
77
  initialMeanProportionInterval: { min: 0.05, max: 0.9 },
77
78
  },
78
- play: async ({ canvas }) => {
79
- await waitFor(async () => {
80
- const annotatedMutation = canvas.getAllByText('C44T')[0];
81
- await expect(annotatedMutation).toBeVisible();
82
- await userEvent.click(annotatedMutation);
83
- });
84
-
85
- await waitFor(() => expect(canvas.getByText('Annotations for C44T')).toBeVisible());
79
+ play: async ({ canvasElement }) => {
80
+ await expectMutationAnnotation(canvasElement, 'C44T');
86
81
  },
87
82
  };
88
83
 
@@ -0,0 +1,13 @@
1
+ import { expect, userEvent, waitFor, within } from '@storybook/test';
2
+
3
+ export async function expectMutationAnnotation(canvasElement: HTMLElement, mutation: string) {
4
+ const canvas = within(canvasElement);
5
+
6
+ await waitFor(async () => {
7
+ const annotatedMutation = canvas.getAllByText(mutation)[0];
8
+ await expect(annotatedMutation).toBeVisible();
9
+ await userEvent.click(annotatedMutation);
10
+ });
11
+
12
+ await waitFor(() => expect(canvas.getByText(`Annotations for ${mutation}`)).toBeVisible());
13
+ }