@genspectrum/dashboard-components 1.10.3 → 1.11.0

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
@@ -71,6 +71,19 @@ export declare type ConfidenceIntervalMethod = default_2.infer<typeof confidence
71
71
 
72
72
  declare const confidenceIntervalMethodSchema: default_2.ZodUnion<[default_2.ZodLiteral<"wilson">, default_2.ZodLiteral<"none">]>;
73
73
 
74
+ export declare type CustomColumn = default_2.infer<typeof customColumnSchema>;
75
+
76
+ declare const customColumnSchema: default_2.ZodObject<{
77
+ header: default_2.ZodString;
78
+ values: default_2.ZodRecord<default_2.ZodString, default_2.ZodUnion<[default_2.ZodString, default_2.ZodNumber]>>;
79
+ }, "strip", default_2.ZodTypeAny, {
80
+ values: Record<string, string | number>;
81
+ header: string;
82
+ }, {
83
+ values: Record<string, string | number>;
84
+ header: string;
85
+ }>;
86
+
74
87
  export declare type DateRangeOption = default_2.infer<typeof dateRangeOptionSchema>;
75
88
 
76
89
  export declare class DateRangeOptionChangedEvent extends CustomEvent<DateRangeValue> {
@@ -958,7 +971,11 @@ declare global {
958
971
 
959
972
  declare global {
960
973
  interface HTMLElementTagNameMap {
961
- 'gs-mutation-comparison-component': MutationComparisonComponent;
974
+ 'gs-date-range-filter': DateRangeFilterComponent;
975
+ }
976
+ interface HTMLElementEventMap {
977
+ [gsEventNames.dateRangeFilterChanged]: CustomEvent<Record<string, string>>;
978
+ [gsEventNames.dateRangeOptionChanged]: DateRangeOptionChangedEvent;
962
979
  }
963
980
  }
964
981
 
@@ -966,7 +983,7 @@ declare global {
966
983
  declare global {
967
984
  namespace JSX {
968
985
  interface IntrinsicElements {
969
- 'gs-mutation-comparison-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
986
+ 'gs-date-range-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
970
987
  }
971
988
  }
972
989
  }
@@ -974,7 +991,10 @@ declare global {
974
991
 
975
992
  declare global {
976
993
  interface HTMLElementTagNameMap {
977
- 'gs-genome-data-viewer': GenomeDataViewerComponent;
994
+ 'gs-location-filter': LocationFilterComponent;
995
+ }
996
+ interface HTMLElementEventMap {
997
+ [gsEventNames.locationChanged]: LocationChangedEvent;
978
998
  }
979
999
  }
980
1000
 
@@ -982,7 +1002,7 @@ declare global {
982
1002
  declare global {
983
1003
  namespace JSX {
984
1004
  interface IntrinsicElements {
985
- 'gs-genome-data-viewer': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1005
+ 'gs-location-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
986
1006
  }
987
1007
  }
988
1008
  }
@@ -990,7 +1010,10 @@ declare global {
990
1010
 
991
1011
  declare global {
992
1012
  interface HTMLElementTagNameMap {
993
- 'gs-mutations': MutationsComponent;
1013
+ 'gs-text-filter': TextFilterComponent;
1014
+ }
1015
+ interface HTMLElementEventMap {
1016
+ [gsEventNames.textFilterChanged]: TextFilterChangedEvent;
994
1017
  }
995
1018
  }
996
1019
 
@@ -998,7 +1021,7 @@ declare global {
998
1021
  declare global {
999
1022
  namespace JSX {
1000
1023
  interface IntrinsicElements {
1001
- 'gs-mutations': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1024
+ 'gs-text-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1002
1025
  }
1003
1026
  }
1004
1027
  }
@@ -1006,7 +1029,11 @@ declare global {
1006
1029
 
1007
1030
  declare global {
1008
1031
  interface HTMLElementTagNameMap {
1009
- 'gs-prevalence-over-time': PrevalenceOverTimeComponent;
1032
+ 'gs-lineage-filter': LineageFilterComponent;
1033
+ }
1034
+ interface HTMLElementEventMap {
1035
+ [gsEventNames.lineageFilterChanged]: LineageFilterChangedEvent;
1036
+ [gsEventNames.lineageFilterMultiChanged]: LineageMultiFilterChangedEvent;
1010
1037
  }
1011
1038
  }
1012
1039
 
@@ -1014,7 +1041,7 @@ declare global {
1014
1041
  declare global {
1015
1042
  namespace JSX {
1016
1043
  interface IntrinsicElements {
1017
- 'gs-prevalence-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1044
+ 'gs-lineage-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1018
1045
  }
1019
1046
  }
1020
1047
  }
@@ -1022,7 +1049,10 @@ declare global {
1022
1049
 
1023
1050
  declare global {
1024
1051
  interface HTMLElementTagNameMap {
1025
- 'gs-relative-growth-advantage': RelativeGrowthAdvantageComponent;
1052
+ 'gs-mutation-filter': MutationFilterComponent;
1053
+ }
1054
+ interface HTMLElementEventMap {
1055
+ [gsEventNames.mutationFilterChanged]: CustomEvent<MutationsFilter>;
1026
1056
  }
1027
1057
  }
1028
1058
 
@@ -1030,7 +1060,7 @@ declare global {
1030
1060
  declare global {
1031
1061
  namespace JSX {
1032
1062
  interface IntrinsicElements {
1033
- 'gs-relative-growth-advantage': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1063
+ 'gs-mutation-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1034
1064
  }
1035
1065
  }
1036
1066
  }
@@ -1038,7 +1068,11 @@ declare global {
1038
1068
 
1039
1069
  declare global {
1040
1070
  interface HTMLElementTagNameMap {
1041
- 'gs-aggregate': AggregateComponent;
1071
+ 'gs-number-range-filter': NumberRangeFilterComponent;
1072
+ }
1073
+ interface HTMLElementEventMap {
1074
+ [gsEventNames.numberRangeFilterChanged]: NumberRangeFilterChangedEvent;
1075
+ [gsEventNames.numberRangeValueChanged]: NumberRangeValueChangedEvent;
1042
1076
  }
1043
1077
  }
1044
1078
 
@@ -1046,7 +1080,7 @@ declare global {
1046
1080
  declare global {
1047
1081
  namespace JSX {
1048
1082
  interface IntrinsicElements {
1049
- 'gs-aggregate': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1083
+ 'gs-number-range-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1050
1084
  }
1051
1085
  }
1052
1086
  }
@@ -1054,7 +1088,7 @@ declare global {
1054
1088
 
1055
1089
  declare global {
1056
1090
  interface HTMLElementTagNameMap {
1057
- 'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
1091
+ 'gs-genome-data-viewer': GenomeDataViewerComponent;
1058
1092
  }
1059
1093
  }
1060
1094
 
@@ -1062,7 +1096,7 @@ declare global {
1062
1096
  declare global {
1063
1097
  namespace JSX {
1064
1098
  interface IntrinsicElements {
1065
- 'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1099
+ 'gs-genome-data-viewer': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1066
1100
  }
1067
1101
  }
1068
1102
  }
@@ -1070,7 +1104,7 @@ declare global {
1070
1104
 
1071
1105
  declare global {
1072
1106
  interface HTMLElementTagNameMap {
1073
- 'gs-mutations-over-time': MutationsOverTimeComponent;
1107
+ 'gs-mutation-comparison-component': MutationComparisonComponent;
1074
1108
  }
1075
1109
  }
1076
1110
 
@@ -1078,7 +1112,7 @@ declare global {
1078
1112
  declare global {
1079
1113
  namespace JSX {
1080
1114
  interface IntrinsicElements {
1081
- 'gs-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1115
+ 'gs-mutation-comparison-component': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1082
1116
  }
1083
1117
  }
1084
1118
  }
@@ -1086,7 +1120,7 @@ declare global {
1086
1120
 
1087
1121
  declare global {
1088
1122
  interface HTMLElementTagNameMap {
1089
- 'gs-sequences-by-location': SequencesByLocationComponent;
1123
+ 'gs-mutations': MutationsComponent;
1090
1124
  }
1091
1125
  }
1092
1126
 
@@ -1094,7 +1128,7 @@ declare global {
1094
1128
  declare global {
1095
1129
  namespace JSX {
1096
1130
  interface IntrinsicElements {
1097
- 'gs-sequences-by-location': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1131
+ 'gs-mutations': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1098
1132
  }
1099
1133
  }
1100
1134
  }
@@ -1102,11 +1136,7 @@ declare global {
1102
1136
 
1103
1137
  declare global {
1104
1138
  interface HTMLElementTagNameMap {
1105
- 'gs-date-range-filter': DateRangeFilterComponent;
1106
- }
1107
- interface HTMLElementEventMap {
1108
- [gsEventNames.dateRangeFilterChanged]: CustomEvent<Record<string, string>>;
1109
- [gsEventNames.dateRangeOptionChanged]: DateRangeOptionChangedEvent;
1139
+ 'gs-prevalence-over-time': PrevalenceOverTimeComponent;
1110
1140
  }
1111
1141
  }
1112
1142
 
@@ -1114,7 +1144,7 @@ declare global {
1114
1144
  declare global {
1115
1145
  namespace JSX {
1116
1146
  interface IntrinsicElements {
1117
- 'gs-date-range-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1147
+ 'gs-prevalence-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1118
1148
  }
1119
1149
  }
1120
1150
  }
@@ -1122,7 +1152,7 @@ declare global {
1122
1152
 
1123
1153
  declare global {
1124
1154
  interface HTMLElementTagNameMap {
1125
- 'gs-statistics': StatisticsComponent;
1155
+ 'gs-relative-growth-advantage': RelativeGrowthAdvantageComponent;
1126
1156
  }
1127
1157
  }
1128
1158
 
@@ -1130,7 +1160,7 @@ declare global {
1130
1160
  declare global {
1131
1161
  namespace JSX {
1132
1162
  interface IntrinsicElements {
1133
- 'gs-statistics': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1163
+ 'gs-relative-growth-advantage': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1134
1164
  }
1135
1165
  }
1136
1166
  }
@@ -1138,10 +1168,7 @@ declare global {
1138
1168
 
1139
1169
  declare global {
1140
1170
  interface HTMLElementTagNameMap {
1141
- 'gs-text-filter': TextFilterComponent;
1142
- }
1143
- interface HTMLElementEventMap {
1144
- [gsEventNames.textFilterChanged]: TextFilterChangedEvent;
1171
+ 'gs-aggregate': AggregateComponent;
1145
1172
  }
1146
1173
  }
1147
1174
 
@@ -1149,7 +1176,7 @@ declare global {
1149
1176
  declare global {
1150
1177
  namespace JSX {
1151
1178
  interface IntrinsicElements {
1152
- 'gs-text-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1179
+ 'gs-aggregate': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1153
1180
  }
1154
1181
  }
1155
1182
  }
@@ -1157,10 +1184,7 @@ declare global {
1157
1184
 
1158
1185
  declare global {
1159
1186
  interface HTMLElementTagNameMap {
1160
- 'gs-location-filter': LocationFilterComponent;
1161
- }
1162
- interface HTMLElementEventMap {
1163
- [gsEventNames.locationChanged]: LocationChangedEvent;
1187
+ 'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
1164
1188
  }
1165
1189
  }
1166
1190
 
@@ -1168,7 +1192,7 @@ declare global {
1168
1192
  declare global {
1169
1193
  namespace JSX {
1170
1194
  interface IntrinsicElements {
1171
- 'gs-location-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1195
+ 'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1172
1196
  }
1173
1197
  }
1174
1198
  }
@@ -1176,10 +1200,7 @@ declare global {
1176
1200
 
1177
1201
  declare global {
1178
1202
  interface HTMLElementTagNameMap {
1179
- 'gs-mutation-filter': MutationFilterComponent;
1180
- }
1181
- interface HTMLElementEventMap {
1182
- [gsEventNames.mutationFilterChanged]: CustomEvent<MutationsFilter>;
1203
+ 'gs-mutations-over-time': MutationsOverTimeComponent;
1183
1204
  }
1184
1205
  }
1185
1206
 
@@ -1187,7 +1208,7 @@ declare global {
1187
1208
  declare global {
1188
1209
  namespace JSX {
1189
1210
  interface IntrinsicElements {
1190
- 'gs-mutation-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1211
+ 'gs-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1191
1212
  }
1192
1213
  }
1193
1214
  }
@@ -1195,11 +1216,7 @@ declare global {
1195
1216
 
1196
1217
  declare global {
1197
1218
  interface HTMLElementTagNameMap {
1198
- 'gs-number-range-filter': NumberRangeFilterComponent;
1199
- }
1200
- interface HTMLElementEventMap {
1201
- [gsEventNames.numberRangeFilterChanged]: NumberRangeFilterChangedEvent;
1202
- [gsEventNames.numberRangeValueChanged]: NumberRangeValueChangedEvent;
1219
+ 'gs-sequences-by-location': SequencesByLocationComponent;
1203
1220
  }
1204
1221
  }
1205
1222
 
@@ -1207,7 +1224,7 @@ declare global {
1207
1224
  declare global {
1208
1225
  namespace JSX {
1209
1226
  interface IntrinsicElements {
1210
- 'gs-number-range-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1227
+ 'gs-sequences-by-location': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1211
1228
  }
1212
1229
  }
1213
1230
  }
@@ -1215,11 +1232,7 @@ declare global {
1215
1232
 
1216
1233
  declare global {
1217
1234
  interface HTMLElementTagNameMap {
1218
- 'gs-lineage-filter': LineageFilterComponent;
1219
- }
1220
- interface HTMLElementEventMap {
1221
- [gsEventNames.lineageFilterChanged]: LineageFilterChangedEvent;
1222
- [gsEventNames.lineageFilterMultiChanged]: LineageMultiFilterChangedEvent;
1235
+ 'gs-statistics': StatisticsComponent;
1223
1236
  }
1224
1237
  }
1225
1238
 
@@ -1227,7 +1240,7 @@ declare global {
1227
1240
  declare global {
1228
1241
  namespace JSX {
1229
1242
  interface IntrinsicElements {
1230
- 'gs-lineage-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1243
+ 'gs-statistics': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
1231
1244
  }
1232
1245
  }
1233
1246
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genspectrum/dashboard-components",
3
- "version": "1.10.3",
3
+ "version": "1.11.0",
4
4
  "description": "GenSpectrum web components for building dashboards",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-only",
@@ -45,7 +45,7 @@ export const NoValue: StoryObj<MutationsOverTimeGridTooltipProps> = {
45
45
  const canvas = within(canvasElement);
46
46
 
47
47
  await expect(canvas.getByText('2025', { exact: true })).toBeVisible();
48
- await expect(canvas.getByText('(2025-01-01 - 2025-12-31)')).toBeVisible();
48
+ await expect(canvas.getByText('2025-01-01 - 2025-12-31')).toBeVisible();
49
49
  await expect(canvas.getByText('A500-')).toBeVisible();
50
50
  await expect(canvas.getByText('No data')).toBeVisible();
51
51
  },
@@ -65,9 +65,13 @@ export const WithValue: StoryObj<MutationsOverTimeGridTooltipProps> = {
65
65
  play: async ({ canvasElement }) => {
66
66
  const canvas = within(canvasElement);
67
67
 
68
- await expect(canvas.getByText('Proportion: 50.00%')).toBeVisible();
69
- await expect(canvas.getByText('300 samples are in the timeframe')).toBeVisible();
70
- await expect(canvas.getByText('200 have coverage, of those 100 have the mutation')).toBeVisible();
68
+ await expect(canvas.getByText('50.00%')).toBeVisible();
69
+ await expect(canvas.getByText('100')).toBeVisible();
70
+ await expect(canvas.getByText('have the mutation A500- out of')).toBeVisible();
71
+ await expect(canvas.getByText('200')).toBeVisible();
72
+ await expect(canvas.getByText('with coverage at position 500.')).toBeVisible();
73
+ await expect(canvas.getByText('300')).toBeVisible();
74
+ await expect(canvas.getByText('total in this date range.')).toBeVisible();
71
75
  },
72
76
  };
73
77
 
@@ -83,9 +87,10 @@ export const WithValueBelowThreshold: StoryObj<MutationsOverTimeGridTooltipProps
83
87
  play: async ({ canvasElement }) => {
84
88
  const canvas = within(canvasElement);
85
89
 
86
- await expect(canvas.getByText('Proportion: <0.10%')).toBeVisible();
87
- await expect(canvas.getByText('300 samples are in the timeframe')).toBeVisible();
88
- await expect(canvas.getByText('none or less than 0.10% have the mutation')).toBeVisible();
90
+ await expect(canvas.getByText('<0.10%')).toBeVisible();
91
+ await expect(canvas.getByText('None or less than 0.10% have the mutation.')).toBeVisible();
92
+ await expect(canvas.getByText('300')).toBeVisible();
93
+ await expect(canvas.getByText('total in this date range.')).toBeVisible();
89
94
  },
90
95
  };
91
96
 
@@ -101,8 +106,8 @@ export const WithWastewaterValue: StoryObj<MutationsOverTimeGridTooltipProps> =
101
106
  play: async ({ canvasElement }) => {
102
107
  const canvas = within(canvasElement);
103
108
 
104
- await expect(canvas.getByText('Proportion: 50.00%')).toBeVisible();
105
- await expect(canvas.queryByText('samples are in the timeframe')).not.toBeInTheDocument();
106
- await expect(canvas.queryByText('have coverage')).not.toBeInTheDocument();
109
+ await expect(canvas.getByText('50.00%')).toBeVisible();
110
+ await expect(canvas.queryByText('total in this date range.')).not.toBeInTheDocument();
111
+ await expect(canvas.queryByText('coverage')).not.toBeInTheDocument();
107
112
  },
108
113
  };
@@ -21,60 +21,66 @@ export const MutationsOverTimeGridTooltip: FunctionComponent<MutationsOverTimeGr
21
21
  }: MutationsOverTimeGridTooltipProps) => {
22
22
  const dateClass = toTemporalClass(date);
23
23
 
24
- return (
25
- <div className='text-center'>
26
- <p>
27
- <span className='font-bold'>{dateClass.englishName()}</span>
28
- </p>
29
- <p>({timeIntervalDisplay(dateClass)})</p>
30
- <p>{mutation.code}</p>
31
- <TooltipValueDescription value={value} />
32
- </div>
33
- );
34
- };
35
-
36
- const TooltipValueDescription: FunctionComponent<{ value: MutationOverTimeMutationValue }> = ({ value }) => {
37
- if (value === null) {
38
- return <p>No data</p>;
39
- }
40
-
41
- const proportion =
42
- value.type === 'belowThreshold'
43
- ? `<${formatProportion(MUTATIONS_OVER_TIME_MIN_PROPORTION)}`
44
- : formatProportion(value.proportion);
24
+ const proportionText =
25
+ value === null
26
+ ? 'No data'
27
+ : value.type === 'belowThreshold'
28
+ ? `<${formatProportion(MUTATIONS_OVER_TIME_MIN_PROPORTION)}`
29
+ : formatProportion(value.proportion);
45
30
 
46
31
  return (
47
- <>
48
- <p>Proportion: {proportion}</p>
49
- <TooltipValueCountsDescription value={value} />
50
- </>
32
+ <div>
33
+ <div className='flex flex-row justify-between gap-4 items-baseline'>
34
+ <div className='flex flex-col text-left'>
35
+ <span className='font-bold'>{mutation.code}</span>
36
+ <span>{proportionText}</span>
37
+ </div>
38
+ <div className='flex flex-col text-right'>
39
+ <span className='font-bold'>{dateClass.englishName()}</span>
40
+ <span className='text-gray-600'>{timeIntervalDisplay(dateClass)}</span>
41
+ </div>
42
+ </div>
43
+ {value !== null && (
44
+ <TooltipValueCountsDescription
45
+ value={value}
46
+ mutationCode={mutation.code}
47
+ mutationPosition={mutation.position}
48
+ />
49
+ )}
50
+ </div>
51
51
  );
52
52
  };
53
53
 
54
54
  const TooltipValueCountsDescription: FunctionComponent<{
55
55
  value: NonNullable<MutationOverTimeMutationValue>;
56
- }> = ({ value }) => {
57
- switch (value.type) {
58
- case 'wastewaterValue':
59
- return;
60
- case 'belowThreshold':
61
- return (
62
- <>
63
- <p>{value.totalCount} samples are in the timeframe</p>
64
- <p>none or less than {formatProportion(MUTATIONS_OVER_TIME_MIN_PROPORTION)} have the mutation</p>
65
- </>
66
- );
67
- case 'value':
68
- return (
56
+ mutationCode: string;
57
+ mutationPosition: number;
58
+ }> = ({ value, mutationCode, mutationPosition }) => {
59
+ if (value.type === 'wastewaterValue') {
60
+ return;
61
+ }
62
+ return (
63
+ <div className='mt-2'>
64
+ {value.type === 'belowThreshold' ? (
65
+ <p className='text-gray-600'>
66
+ None or less than {formatProportion(MUTATIONS_OVER_TIME_MIN_PROPORTION)} have the mutation.
67
+ </p>
68
+ ) : (
69
69
  <>
70
- <p>{value.totalCount} samples are in the timeframe</p>
71
70
  <p>
72
- {totalCountWithCoverage(value.count, value.proportion)} have coverage, of those {value.count}{' '}
73
- have the mutation
71
+ {value.count} <span className='text-gray-600'>have the mutation {mutationCode} out of</span>
72
+ </p>
73
+ <p>
74
+ {totalCountWithCoverage(value.count, value.proportion)}{' '}
75
+ <span className='text-gray-600'>with coverage at position {mutationPosition}.</span>
74
76
  </p>
75
77
  </>
76
- );
77
- }
78
+ )}
79
+ <p>
80
+ {value.totalCount} <span className='text-gray-600'>total in this date range.</span>
81
+ </p>
82
+ </div>
83
+ );
78
84
  };
79
85
 
80
86
  function totalCountWithCoverage(count: number, proportion: number) {
@@ -1,5 +1,6 @@
1
1
  import { type FunctionComponent } from 'preact';
2
2
  import { useMemo } from 'preact/hooks';
3
+ import z from 'zod';
3
4
 
4
5
  import { type MutationOverTimeDataMap } from './MutationOverTimeData';
5
6
  import { MutationsOverTimeGridTooltip } from './mutations-over-time-grid-tooltip';
@@ -24,29 +25,45 @@ import {
24
25
 
25
26
  const NON_BREAKING_SPACE = '\u00A0';
26
27
 
28
+ export const customColumnSchema = z.object({
29
+ header: z.string(),
30
+ values: z.record(z.string(), z.union([z.string(), z.number()])),
31
+ });
32
+ export type CustomColumn = z.infer<typeof customColumnSchema>;
33
+
27
34
  export interface MutationsOverTimeGridProps {
28
35
  data: MutationOverTimeDataMap;
29
36
  colorScale: ColorScale;
30
37
  sequenceType: SequenceType;
31
38
  pageSizes: PageSizes;
39
+ customColumns?: CustomColumn[];
32
40
  tooltipPortalTarget: HTMLElement | null;
33
41
  }
34
42
 
35
- type RowType = { mutation: Substitution | Deletion; values: (MutationOverTimeMutationValue | undefined)[] };
43
+ type RowType = {
44
+ mutation: Substitution | Deletion;
45
+ values: (MutationOverTimeMutationValue | undefined)[];
46
+ customValues: (string | number | undefined)[];
47
+ };
48
+
49
+ const EMPTY_COLUMNS: CustomColumn[] = [];
36
50
 
37
51
  const MutationsOverTimeGrid: FunctionComponent<MutationsOverTimeGridProps> = ({
38
52
  data,
39
53
  colorScale,
40
54
  sequenceType,
41
55
  pageSizes,
56
+ customColumns = EMPTY_COLUMNS,
42
57
  tooltipPortalTarget,
43
58
  }) => {
44
59
  const tableData = useMemo(() => {
45
60
  const allMutations = data.getFirstAxisKeys();
46
- return data.getAsArray().map((row, index) => {
47
- return { mutation: allMutations[index], values: [...row] };
61
+ return data.getAsArray().map((row, index): RowType => {
62
+ const mutation = allMutations[index];
63
+ const customValues = customColumns.map((col) => col.values[mutation.code]);
64
+ return { mutation, values: [...row], customValues };
48
65
  });
49
- }, [data]);
66
+ }, [data, customColumns]);
50
67
 
51
68
  const columns = useMemo(() => {
52
69
  const columnHelper = createColumnHelper<RowType>();
@@ -65,6 +82,17 @@ const MutationsOverTimeGrid: FunctionComponent<MutationsOverTimeGridProps> = ({
65
82
  },
66
83
  });
67
84
 
85
+ const customColumnHeaders = customColumns.map((customCol, index) => {
86
+ return columnHelper.accessor((row) => row.customValues[index], {
87
+ id: `custom-${index}`,
88
+ header: () => <span>{customCol.header}</span>,
89
+ cell: ({ getValue }) => {
90
+ const value = getValue();
91
+ return <div className={'text-center'}>{value ?? ''}</div>;
92
+ },
93
+ });
94
+ });
95
+
68
96
  const dateHeaders = dates.map((date, index) => {
69
97
  return columnHelper.accessor((row) => row.values[index], {
70
98
  id: `date-${index}`,
@@ -102,8 +130,8 @@ const MutationsOverTimeGrid: FunctionComponent<MutationsOverTimeGridProps> = ({
102
130
  });
103
131
  });
104
132
 
105
- return [mutationHeader, ...dateHeaders];
106
- }, [colorScale, data, sequenceType, tooltipPortalTarget]);
133
+ return [mutationHeader, ...customColumnHeaders, ...dateHeaders];
134
+ }, [colorScale, data, sequenceType, customColumns, tooltipPortalTarget]);
107
135
 
108
136
  const { pageSize } = usePageSizeContext();
109
137
  const table = usePreactTable({
@@ -38,6 +38,7 @@ const meta: Meta<MutationsOverTimeProps> = {
38
38
  hideGaps: { control: 'boolean' },
39
39
  pageSizes: { control: 'object' },
40
40
  useNewEndpoint: { control: 'boolean' },
41
+ customColumns: { control: 'object' },
41
42
  },
42
43
  parameters: {
43
44
  fetchMock: {},
@@ -196,6 +197,32 @@ export const UsesMutationFilter: StoryObj<MutationsOverTimeProps> = {
196
197
  },
197
198
  };
198
199
 
200
+ export const WithCustomColumns: StoryObj<MutationsOverTimeProps> = {
201
+ ...Default,
202
+ args: {
203
+ ...Default.args,
204
+ displayMutations: ['A19722G', 'G21641T', 'T21653-'],
205
+ customColumns: [
206
+ {
207
+ header: 'Jaccard Index',
208
+ values: {
209
+ A19722G: 0.75,
210
+ G21641T: 'Foobar',
211
+ },
212
+ },
213
+ ],
214
+ },
215
+ play: async ({ canvas }) => {
216
+ await waitFor(() => expect(canvas.getByText('Jaccard Index')).toBeVisible(), {
217
+ timeout: 5000,
218
+ });
219
+
220
+ await waitFor(() => expect(canvas.getByText('0.75')).toBeVisible());
221
+
222
+ await waitFor(() => expect(canvas.getByText('Foobar')).toBeVisible());
223
+ },
224
+ };
225
+
199
226
  async function expectMutationOnPage(canvas: Canvas, mutation: string) {
200
227
  await waitFor(async () => {
201
228
  const mutationOnFirstPage = canvas.getAllByText(mutation)[0];
@@ -11,7 +11,7 @@ import {
11
11
  type MutationFilter,
12
12
  } from './getFilteredMutationsOverTimeData';
13
13
  import { type MutationOverTimeWorkerResponse } from './mutationOverTimeWorker';
14
- import MutationsOverTimeGrid from './mutations-over-time-grid';
14
+ import MutationsOverTimeGrid, { customColumnSchema } from './mutations-over-time-grid';
15
15
  import { type MutationOverTimeQuery } from '../../query/queryMutationsOverTime';
16
16
  import {
17
17
  lapisFilterSchema,
@@ -66,6 +66,7 @@ const mutationOverTimeSchema = z.object({
66
66
  width: z.string(),
67
67
  height: z.string().optional(),
68
68
  pageSizes: pageSizesSchema,
69
+ customColumns: z.array(customColumnSchema).optional(),
69
70
  });
70
71
  export type MutationsOverTimeProps = z.infer<typeof mutationOverTimeSchema>;
71
72
 
@@ -204,6 +205,7 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
204
205
  colorScale={colorScale}
205
206
  sequenceType={originalComponentProps.sequenceType}
206
207
  pageSizes={originalComponentProps.pageSizes}
208
+ customColumns={originalComponentProps.customColumns}
207
209
  tooltipPortalTarget={tooltipPortalTarget}
208
210
  />
209
211
  ),
@@ -51,3 +51,4 @@ export {
51
51
  } from './preact/numberRangeFilter/NumberRangeFilterChangedEvent';
52
52
 
53
53
  export { type MeanProportionInterval } from './preact/mutationsOverTime/mutations-over-time';
54
+ export { type CustomColumn } from './preact/mutationsOverTime/mutations-over-time-grid';