@genspectrum/dashboard-components 1.10.3 → 1.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/custom-elements.json +38 -3
- package/dist/assets/{mutationOverTimeWorker-dhufsWQ2.js.map → mutationOverTimeWorker-CQQFRoK4.js.map} +1 -1
- package/dist/components.d.ts +40 -32
- package/dist/components.js +144 -66
- package/dist/components.js.map +1 -1
- package/dist/util.d.ts +45 -32
- package/package.json +1 -1
- package/src/preact/mutationsOverTime/getFilteredMutationsOverTimeData.ts +1 -1
- package/src/preact/mutationsOverTime/mutations-over-time-grid-tooltip.stories.tsx +62 -10
- package/src/preact/mutationsOverTime/mutations-over-time-grid-tooltip.tsx +93 -51
- package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +36 -8
- package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +27 -0
- package/src/preact/mutationsOverTime/mutations-over-time.tsx +5 -3
- package/src/query/queryMutationsOverTime.ts +21 -2
- package/src/query/queryMutationsOverTimeNewEndpoint.spec.ts +23 -23
- package/src/utilEntrypoint.ts +1 -0
- package/src/web-components/input/gs-lineage-filter.stories.ts +9 -1
- package/src/web-components/visualization/gs-mutations-over-time.spec-d.ts +3 -0
- package/src/web-components/visualization/gs-mutations-over-time.stories.ts +21 -0
- package/src/web-components/visualization/gs-mutations-over-time.tsx +8 -0
- package/standalone-bundle/assets/{mutationOverTimeWorker-CGqPKySO.js.map → mutationOverTimeWorker-DIpJukJC.js.map} +1 -1
- package/standalone-bundle/dashboard-components.js +2953 -2880
- package/standalone-bundle/dashboard-components.js.map +1 -1
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> {
|
|
@@ -942,7 +955,7 @@ declare global {
|
|
|
942
955
|
|
|
943
956
|
declare global {
|
|
944
957
|
interface HTMLElementTagNameMap {
|
|
945
|
-
'gs-
|
|
958
|
+
'gs-genome-data-viewer': GenomeDataViewerComponent;
|
|
946
959
|
}
|
|
947
960
|
}
|
|
948
961
|
|
|
@@ -950,7 +963,7 @@ declare global {
|
|
|
950
963
|
declare global {
|
|
951
964
|
namespace JSX {
|
|
952
965
|
interface IntrinsicElements {
|
|
953
|
-
'gs-
|
|
966
|
+
'gs-genome-data-viewer': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
954
967
|
}
|
|
955
968
|
}
|
|
956
969
|
}
|
|
@@ -974,7 +987,7 @@ declare global {
|
|
|
974
987
|
|
|
975
988
|
declare global {
|
|
976
989
|
interface HTMLElementTagNameMap {
|
|
977
|
-
'gs-
|
|
990
|
+
'gs-mutations': MutationsComponent;
|
|
978
991
|
}
|
|
979
992
|
}
|
|
980
993
|
|
|
@@ -982,7 +995,7 @@ declare global {
|
|
|
982
995
|
declare global {
|
|
983
996
|
namespace JSX {
|
|
984
997
|
interface IntrinsicElements {
|
|
985
|
-
'gs-
|
|
998
|
+
'gs-mutations': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
986
999
|
}
|
|
987
1000
|
}
|
|
988
1001
|
}
|
|
@@ -990,7 +1003,7 @@ declare global {
|
|
|
990
1003
|
|
|
991
1004
|
declare global {
|
|
992
1005
|
interface HTMLElementTagNameMap {
|
|
993
|
-
'gs-
|
|
1006
|
+
'gs-prevalence-over-time': PrevalenceOverTimeComponent;
|
|
994
1007
|
}
|
|
995
1008
|
}
|
|
996
1009
|
|
|
@@ -998,7 +1011,7 @@ declare global {
|
|
|
998
1011
|
declare global {
|
|
999
1012
|
namespace JSX {
|
|
1000
1013
|
interface IntrinsicElements {
|
|
1001
|
-
'gs-
|
|
1014
|
+
'gs-prevalence-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1002
1015
|
}
|
|
1003
1016
|
}
|
|
1004
1017
|
}
|
|
@@ -1006,7 +1019,7 @@ declare global {
|
|
|
1006
1019
|
|
|
1007
1020
|
declare global {
|
|
1008
1021
|
interface HTMLElementTagNameMap {
|
|
1009
|
-
'gs-
|
|
1022
|
+
'gs-aggregate': AggregateComponent;
|
|
1010
1023
|
}
|
|
1011
1024
|
}
|
|
1012
1025
|
|
|
@@ -1014,7 +1027,7 @@ declare global {
|
|
|
1014
1027
|
declare global {
|
|
1015
1028
|
namespace JSX {
|
|
1016
1029
|
interface IntrinsicElements {
|
|
1017
|
-
'gs-
|
|
1030
|
+
'gs-aggregate': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1018
1031
|
}
|
|
1019
1032
|
}
|
|
1020
1033
|
}
|
|
@@ -1022,7 +1035,7 @@ declare global {
|
|
|
1022
1035
|
|
|
1023
1036
|
declare global {
|
|
1024
1037
|
interface HTMLElementTagNameMap {
|
|
1025
|
-
'gs-
|
|
1038
|
+
'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
|
|
1026
1039
|
}
|
|
1027
1040
|
}
|
|
1028
1041
|
|
|
@@ -1030,7 +1043,7 @@ declare global {
|
|
|
1030
1043
|
declare global {
|
|
1031
1044
|
namespace JSX {
|
|
1032
1045
|
interface IntrinsicElements {
|
|
1033
|
-
'gs-
|
|
1046
|
+
'gs-number-sequences-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1034
1047
|
}
|
|
1035
1048
|
}
|
|
1036
1049
|
}
|
|
@@ -1038,7 +1051,7 @@ declare global {
|
|
|
1038
1051
|
|
|
1039
1052
|
declare global {
|
|
1040
1053
|
interface HTMLElementTagNameMap {
|
|
1041
|
-
'gs-
|
|
1054
|
+
'gs-relative-growth-advantage': RelativeGrowthAdvantageComponent;
|
|
1042
1055
|
}
|
|
1043
1056
|
}
|
|
1044
1057
|
|
|
@@ -1046,7 +1059,7 @@ declare global {
|
|
|
1046
1059
|
declare global {
|
|
1047
1060
|
namespace JSX {
|
|
1048
1061
|
interface IntrinsicElements {
|
|
1049
|
-
'gs-
|
|
1062
|
+
'gs-relative-growth-advantage': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1050
1063
|
}
|
|
1051
1064
|
}
|
|
1052
1065
|
}
|
|
@@ -1054,7 +1067,7 @@ declare global {
|
|
|
1054
1067
|
|
|
1055
1068
|
declare global {
|
|
1056
1069
|
interface HTMLElementTagNameMap {
|
|
1057
|
-
'gs-
|
|
1070
|
+
'gs-mutations-over-time': MutationsOverTimeComponent;
|
|
1058
1071
|
}
|
|
1059
1072
|
}
|
|
1060
1073
|
|
|
@@ -1062,7 +1075,7 @@ declare global {
|
|
|
1062
1075
|
declare global {
|
|
1063
1076
|
namespace JSX {
|
|
1064
1077
|
interface IntrinsicElements {
|
|
1065
|
-
'gs-
|
|
1078
|
+
'gs-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1066
1079
|
}
|
|
1067
1080
|
}
|
|
1068
1081
|
}
|
|
@@ -1070,7 +1083,7 @@ declare global {
|
|
|
1070
1083
|
|
|
1071
1084
|
declare global {
|
|
1072
1085
|
interface HTMLElementTagNameMap {
|
|
1073
|
-
'gs-
|
|
1086
|
+
'gs-sequences-by-location': SequencesByLocationComponent;
|
|
1074
1087
|
}
|
|
1075
1088
|
}
|
|
1076
1089
|
|
|
@@ -1078,7 +1091,7 @@ declare global {
|
|
|
1078
1091
|
declare global {
|
|
1079
1092
|
namespace JSX {
|
|
1080
1093
|
interface IntrinsicElements {
|
|
1081
|
-
'gs-
|
|
1094
|
+
'gs-sequences-by-location': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1082
1095
|
}
|
|
1083
1096
|
}
|
|
1084
1097
|
}
|
|
@@ -1086,7 +1099,7 @@ declare global {
|
|
|
1086
1099
|
|
|
1087
1100
|
declare global {
|
|
1088
1101
|
interface HTMLElementTagNameMap {
|
|
1089
|
-
'gs-
|
|
1102
|
+
'gs-statistics': StatisticsComponent;
|
|
1090
1103
|
}
|
|
1091
1104
|
}
|
|
1092
1105
|
|
|
@@ -1094,7 +1107,7 @@ declare global {
|
|
|
1094
1107
|
declare global {
|
|
1095
1108
|
namespace JSX {
|
|
1096
1109
|
interface IntrinsicElements {
|
|
1097
|
-
'gs-
|
|
1110
|
+
'gs-statistics': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1098
1111
|
}
|
|
1099
1112
|
}
|
|
1100
1113
|
}
|
|
@@ -1122,7 +1135,10 @@ declare global {
|
|
|
1122
1135
|
|
|
1123
1136
|
declare global {
|
|
1124
1137
|
interface HTMLElementTagNameMap {
|
|
1125
|
-
'gs-
|
|
1138
|
+
'gs-location-filter': LocationFilterComponent;
|
|
1139
|
+
}
|
|
1140
|
+
interface HTMLElementEventMap {
|
|
1141
|
+
[gsEventNames.locationChanged]: LocationChangedEvent;
|
|
1126
1142
|
}
|
|
1127
1143
|
}
|
|
1128
1144
|
|
|
@@ -1130,7 +1146,7 @@ declare global {
|
|
|
1130
1146
|
declare global {
|
|
1131
1147
|
namespace JSX {
|
|
1132
1148
|
interface IntrinsicElements {
|
|
1133
|
-
'gs-
|
|
1149
|
+
'gs-location-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1134
1150
|
}
|
|
1135
1151
|
}
|
|
1136
1152
|
}
|
|
@@ -1157,10 +1173,10 @@ declare global {
|
|
|
1157
1173
|
|
|
1158
1174
|
declare global {
|
|
1159
1175
|
interface HTMLElementTagNameMap {
|
|
1160
|
-
'gs-
|
|
1176
|
+
'gs-mutation-filter': MutationFilterComponent;
|
|
1161
1177
|
}
|
|
1162
1178
|
interface HTMLElementEventMap {
|
|
1163
|
-
[gsEventNames.
|
|
1179
|
+
[gsEventNames.mutationFilterChanged]: CustomEvent<MutationsFilter>;
|
|
1164
1180
|
}
|
|
1165
1181
|
}
|
|
1166
1182
|
|
|
@@ -1168,7 +1184,7 @@ declare global {
|
|
|
1168
1184
|
declare global {
|
|
1169
1185
|
namespace JSX {
|
|
1170
1186
|
interface IntrinsicElements {
|
|
1171
|
-
'gs-
|
|
1187
|
+
'gs-mutation-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1172
1188
|
}
|
|
1173
1189
|
}
|
|
1174
1190
|
}
|
|
@@ -1176,10 +1192,11 @@ declare global {
|
|
|
1176
1192
|
|
|
1177
1193
|
declare global {
|
|
1178
1194
|
interface HTMLElementTagNameMap {
|
|
1179
|
-
'gs-
|
|
1195
|
+
'gs-lineage-filter': LineageFilterComponent;
|
|
1180
1196
|
}
|
|
1181
1197
|
interface HTMLElementEventMap {
|
|
1182
|
-
[gsEventNames.
|
|
1198
|
+
[gsEventNames.lineageFilterChanged]: LineageFilterChangedEvent;
|
|
1199
|
+
[gsEventNames.lineageFilterMultiChanged]: LineageMultiFilterChangedEvent;
|
|
1183
1200
|
}
|
|
1184
1201
|
}
|
|
1185
1202
|
|
|
@@ -1187,7 +1204,7 @@ declare global {
|
|
|
1187
1204
|
declare global {
|
|
1188
1205
|
namespace JSX {
|
|
1189
1206
|
interface IntrinsicElements {
|
|
1190
|
-
'gs-
|
|
1207
|
+
'gs-lineage-filter': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1191
1208
|
}
|
|
1192
1209
|
}
|
|
1193
1210
|
}
|
|
@@ -1215,11 +1232,7 @@ declare global {
|
|
|
1215
1232
|
|
|
1216
1233
|
declare global {
|
|
1217
1234
|
interface HTMLElementTagNameMap {
|
|
1218
|
-
'gs-
|
|
1219
|
-
}
|
|
1220
|
-
interface HTMLElementEventMap {
|
|
1221
|
-
[gsEventNames.lineageFilterChanged]: LineageFilterChangedEvent;
|
|
1222
|
-
[gsEventNames.lineageFilterMultiChanged]: LineageMultiFilterChangedEvent;
|
|
1235
|
+
'gs-wastewater-mutations-over-time': WastewaterMutationsOverTimeComponent;
|
|
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-
|
|
1243
|
+
'gs-wastewater-mutations-over-time': DetailedHTMLProps<HTMLAttributes<HTMLElement>, HTMLElement>;
|
|
1231
1244
|
}
|
|
1232
1245
|
}
|
|
1233
1246
|
}
|
package/package.json
CHANGED
|
@@ -68,7 +68,7 @@ export function getFilteredMutationOverTimeData({
|
|
|
68
68
|
if (hideGaps) {
|
|
69
69
|
const dateRangesToFilterOut = filteredData.getSecondAxisKeys().filter((dateRange) => {
|
|
70
70
|
const vals = filteredData.getColumn(dateRange);
|
|
71
|
-
return !vals.some((v) => v?.type === 'value' && v.totalCount > 0);
|
|
71
|
+
return !vals.some((v) => (v?.type === 'value' || v?.type === 'valueWithCoverage') && v.totalCount > 0);
|
|
72
72
|
});
|
|
73
73
|
dateRangesToFilterOut.forEach((dateRange) => filteredData.deleteColumn(dateRange));
|
|
74
74
|
}
|
|
@@ -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('
|
|
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,60 @@ export const WithValue: StoryObj<MutationsOverTimeGridTooltipProps> = {
|
|
|
65
65
|
play: async ({ canvasElement }) => {
|
|
66
66
|
const canvas = within(canvasElement);
|
|
67
67
|
|
|
68
|
-
await expect(canvas.getByText('
|
|
69
|
-
await expect(canvas.getByText('
|
|
70
|
-
await expect(canvas.getByText('
|
|
68
|
+
await expect(canvas.getByText('50.00%')).toBeVisible();
|
|
69
|
+
await expect(canvas.getByText('100')).toBeVisible();
|
|
70
|
+
await expect(canvas.getByText('have the mutation A500-.')).toBeVisible();
|
|
71
|
+
await expect(canvas.getByText('200')).toBeVisible();
|
|
72
|
+
await expect(canvas.getByText('have coverage at position 500.')).toBeVisible();
|
|
73
|
+
await expect(canvas.getByText('300')).toBeVisible();
|
|
74
|
+
await expect(canvas.getByText('total in this date range.')).toBeVisible();
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const WithValueWithZero: StoryObj<MutationsOverTimeGridTooltipProps> = {
|
|
79
|
+
...Template,
|
|
80
|
+
args: {
|
|
81
|
+
...Template.args,
|
|
82
|
+
value: {
|
|
83
|
+
type: 'value',
|
|
84
|
+
proportion: 0,
|
|
85
|
+
count: 0,
|
|
86
|
+
totalCount: 300,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
play: async ({ canvasElement }) => {
|
|
90
|
+
const canvas = within(canvasElement);
|
|
91
|
+
|
|
92
|
+
await expect(canvas.getByText('0.00%')).toBeVisible();
|
|
93
|
+
await expect(canvas.getByText('0')).toBeVisible();
|
|
94
|
+
await expect(canvas.getByText('have the mutation A500-.')).toBeVisible();
|
|
95
|
+
await expect(canvas.queryByText('with coverage at position 500.')).not.toBeInTheDocument();
|
|
96
|
+
await expect(canvas.getByText('300')).toBeVisible();
|
|
97
|
+
await expect(canvas.getByText('total in this date range.')).toBeVisible();
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export const WithValueWithCoverage: StoryObj<MutationsOverTimeGridTooltipProps> = {
|
|
102
|
+
...Template,
|
|
103
|
+
args: {
|
|
104
|
+
...Template.args,
|
|
105
|
+
value: {
|
|
106
|
+
type: 'valueWithCoverage',
|
|
107
|
+
count: 100,
|
|
108
|
+
coverage: 200,
|
|
109
|
+
totalCount: 300,
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
play: async ({ canvasElement }) => {
|
|
113
|
+
const canvas = within(canvasElement);
|
|
114
|
+
|
|
115
|
+
await expect(canvas.getByText('50.00%')).toBeVisible();
|
|
116
|
+
await expect(canvas.getByText('100')).toBeVisible();
|
|
117
|
+
await expect(canvas.getByText('have the mutation A500- out of')).toBeVisible();
|
|
118
|
+
await expect(canvas.getByText('200')).toBeVisible();
|
|
119
|
+
await expect(canvas.getByText('with coverage at position 500.')).toBeVisible();
|
|
120
|
+
await expect(canvas.getByText('300')).toBeVisible();
|
|
121
|
+
await expect(canvas.getByText('total in this date range.')).toBeVisible();
|
|
71
122
|
},
|
|
72
123
|
};
|
|
73
124
|
|
|
@@ -83,9 +134,10 @@ export const WithValueBelowThreshold: StoryObj<MutationsOverTimeGridTooltipProps
|
|
|
83
134
|
play: async ({ canvasElement }) => {
|
|
84
135
|
const canvas = within(canvasElement);
|
|
85
136
|
|
|
86
|
-
await expect(canvas.getByText('
|
|
87
|
-
await expect(canvas.getByText('
|
|
88
|
-
await expect(canvas.getByText('
|
|
137
|
+
await expect(canvas.getByText('<0.10%')).toBeVisible();
|
|
138
|
+
await expect(canvas.getByText('None or less than 0.10% have the mutation.')).toBeVisible();
|
|
139
|
+
await expect(canvas.getByText('300')).toBeVisible();
|
|
140
|
+
await expect(canvas.getByText('total in this date range.')).toBeVisible();
|
|
89
141
|
},
|
|
90
142
|
};
|
|
91
143
|
|
|
@@ -101,8 +153,8 @@ export const WithWastewaterValue: StoryObj<MutationsOverTimeGridTooltipProps> =
|
|
|
101
153
|
play: async ({ canvasElement }) => {
|
|
102
154
|
const canvas = within(canvasElement);
|
|
103
155
|
|
|
104
|
-
await expect(canvas.getByText('
|
|
105
|
-
await expect(canvas.queryByText('
|
|
106
|
-
await expect(canvas.queryByText('
|
|
156
|
+
await expect(canvas.getByText('50.00%')).toBeVisible();
|
|
157
|
+
await expect(canvas.queryByText('total in this date range.')).not.toBeInTheDocument();
|
|
158
|
+
await expect(canvas.queryByText('coverage')).not.toBeInTheDocument();
|
|
107
159
|
},
|
|
108
160
|
};
|
|
@@ -21,68 +21,110 @@ export const MutationsOverTimeGridTooltip: FunctionComponent<MutationsOverTimeGr
|
|
|
21
21
|
}: MutationsOverTimeGridTooltipProps) => {
|
|
22
22
|
const dateClass = toTemporalClass(date);
|
|
23
23
|
|
|
24
|
-
|
|
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
|
-
};
|
|
24
|
+
let proportionText = 'No data';
|
|
35
25
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
26
|
+
if (value !== null) {
|
|
27
|
+
switch (value.type) {
|
|
28
|
+
case 'belowThreshold': {
|
|
29
|
+
proportionText = `<${formatProportion(MUTATIONS_OVER_TIME_MIN_PROPORTION)}`;
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
case 'value':
|
|
33
|
+
case 'wastewaterValue': {
|
|
34
|
+
proportionText = formatProportion(value.proportion);
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
case 'valueWithCoverage': {
|
|
38
|
+
// value.coverage will always be non-zero if we're in this case
|
|
39
|
+
proportionText = formatProportion(value.count / value.coverage);
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
39
43
|
}
|
|
40
44
|
|
|
41
|
-
const proportion =
|
|
42
|
-
value.type === 'belowThreshold'
|
|
43
|
-
? `<${formatProportion(MUTATIONS_OVER_TIME_MIN_PROPORTION)}`
|
|
44
|
-
: formatProportion(value.proportion);
|
|
45
|
-
|
|
46
45
|
return (
|
|
47
|
-
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
<div>
|
|
47
|
+
<div className='flex flex-row justify-between gap-4 items-baseline'>
|
|
48
|
+
<div className='flex flex-col text-left'>
|
|
49
|
+
<span className='font-bold'>{mutation.code}</span>
|
|
50
|
+
<span>{proportionText}</span>
|
|
51
|
+
</div>
|
|
52
|
+
<div className='flex flex-col text-right'>
|
|
53
|
+
<span className='font-bold'>{dateClass.englishName()}</span>
|
|
54
|
+
<span className='text-gray-600'>{timeIntervalDisplay(dateClass)}</span>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
{value !== null && (
|
|
58
|
+
<TooltipValueCountsDescription
|
|
59
|
+
value={value}
|
|
60
|
+
mutationCode={mutation.code}
|
|
61
|
+
mutationPosition={mutation.position}
|
|
62
|
+
/>
|
|
63
|
+
)}
|
|
64
|
+
</div>
|
|
51
65
|
);
|
|
52
66
|
};
|
|
53
67
|
|
|
54
68
|
const TooltipValueCountsDescription: FunctionComponent<{
|
|
55
69
|
value: NonNullable<MutationOverTimeMutationValue>;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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 (
|
|
69
|
-
<>
|
|
70
|
-
<p>{value.totalCount} samples are in the timeframe</p>
|
|
71
|
-
<p>
|
|
72
|
-
{totalCountWithCoverage(value.count, value.proportion)} have coverage, of those {value.count}{' '}
|
|
73
|
-
have the mutation
|
|
74
|
-
</p>
|
|
75
|
-
</>
|
|
76
|
-
);
|
|
70
|
+
mutationCode: string;
|
|
71
|
+
mutationPosition: number;
|
|
72
|
+
}> = ({ value, mutationCode, mutationPosition }) => {
|
|
73
|
+
if (value.type === 'wastewaterValue') {
|
|
74
|
+
return;
|
|
77
75
|
}
|
|
78
|
-
|
|
76
|
+
return (
|
|
77
|
+
<div className='mt-2'>
|
|
78
|
+
{(() => {
|
|
79
|
+
switch (value.type) {
|
|
80
|
+
case 'belowThreshold':
|
|
81
|
+
return (
|
|
82
|
+
<p className='text-gray-600'>
|
|
83
|
+
None or less than {formatProportion(MUTATIONS_OVER_TIME_MIN_PROPORTION)} have the
|
|
84
|
+
mutation.
|
|
85
|
+
</p>
|
|
86
|
+
);
|
|
79
87
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
88
|
+
case 'value':
|
|
89
|
+
return (
|
|
90
|
+
<>
|
|
91
|
+
<p>
|
|
92
|
+
{value.count}{' '}
|
|
93
|
+
<span className='text-gray-600'>have the mutation {mutationCode}.</span>
|
|
94
|
+
</p>
|
|
95
|
+
{value.proportion > 0 && (
|
|
96
|
+
<p>
|
|
97
|
+
{Math.round(value.count / value.proportion)}{' '}
|
|
98
|
+
<span className='text-gray-600'>
|
|
99
|
+
have coverage at position {mutationPosition}.
|
|
100
|
+
</span>
|
|
101
|
+
</p>
|
|
102
|
+
)}
|
|
103
|
+
</>
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
case 'valueWithCoverage':
|
|
107
|
+
return (
|
|
108
|
+
<>
|
|
109
|
+
<p>
|
|
110
|
+
{value.count}{' '}
|
|
111
|
+
<span className='text-gray-600'>have the mutation {mutationCode} out of</span>
|
|
112
|
+
</p>
|
|
113
|
+
<p>
|
|
114
|
+
{value.coverage}{' '}
|
|
115
|
+
<span className='text-gray-600'>with coverage at position {mutationPosition}.</span>
|
|
116
|
+
</p>
|
|
117
|
+
</>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
})()}
|
|
121
|
+
|
|
122
|
+
<p>
|
|
123
|
+
{value.totalCount} <span className='text-gray-600'>total in this date range.</span>
|
|
124
|
+
</p>
|
|
125
|
+
</div>
|
|
126
|
+
);
|
|
127
|
+
};
|
|
86
128
|
|
|
87
129
|
const timeIntervalDisplay = (date: TemporalClass) => {
|
|
88
130
|
if (date instanceof YearMonthDayClass) {
|
|
@@ -1,9 +1,10 @@
|
|
|
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';
|
|
6
|
-
import { type MutationOverTimeMutationValue } from '../../query/queryMutationsOverTime';
|
|
7
|
+
import { getProportion, type MutationOverTimeMutationValue } from '../../query/queryMutationsOverTime';
|
|
7
8
|
import { type SequenceType } from '../../types';
|
|
8
9
|
import { type Deletion, type Substitution } from '../../utils/mutations';
|
|
9
10
|
import { type Temporal } from '../../utils/temporalClass';
|
|
@@ -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 = {
|
|
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
|
-
|
|
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({
|
|
@@ -183,7 +211,7 @@ const ProportionCell: FunctionComponent<{
|
|
|
183
211
|
colorScale: ColorScale;
|
|
184
212
|
tooltipPortalTarget: HTMLElement | null;
|
|
185
213
|
}> = ({ value, mutation, date, tooltipPosition, colorScale, tooltipPortalTarget }) => {
|
|
186
|
-
const proportion = value
|
|
214
|
+
const proportion = getProportion(value);
|
|
187
215
|
|
|
188
216
|
return (
|
|
189
217
|
<div className={'py-1 w-full h-full'}>
|