@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.
Files changed (23) hide show
  1. package/custom-elements.json +38 -3
  2. package/dist/assets/{mutationOverTimeWorker-dhufsWQ2.js.map → mutationOverTimeWorker-CQQFRoK4.js.map} +1 -1
  3. package/dist/components.d.ts +40 -32
  4. package/dist/components.js +144 -66
  5. package/dist/components.js.map +1 -1
  6. package/dist/util.d.ts +45 -32
  7. package/package.json +1 -1
  8. package/src/preact/mutationsOverTime/getFilteredMutationsOverTimeData.ts +1 -1
  9. package/src/preact/mutationsOverTime/mutations-over-time-grid-tooltip.stories.tsx +62 -10
  10. package/src/preact/mutationsOverTime/mutations-over-time-grid-tooltip.tsx +93 -51
  11. package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +36 -8
  12. package/src/preact/mutationsOverTime/mutations-over-time.stories.tsx +27 -0
  13. package/src/preact/mutationsOverTime/mutations-over-time.tsx +5 -3
  14. package/src/query/queryMutationsOverTime.ts +21 -2
  15. package/src/query/queryMutationsOverTimeNewEndpoint.spec.ts +23 -23
  16. package/src/utilEntrypoint.ts +1 -0
  17. package/src/web-components/input/gs-lineage-filter.stories.ts +9 -1
  18. package/src/web-components/visualization/gs-mutations-over-time.spec-d.ts +3 -0
  19. package/src/web-components/visualization/gs-mutations-over-time.stories.ts +21 -0
  20. package/src/web-components/visualization/gs-mutations-over-time.tsx +8 -0
  21. package/standalone-bundle/assets/{mutationOverTimeWorker-CGqPKySO.js.map → mutationOverTimeWorker-DIpJukJC.js.map} +1 -1
  22. package/standalone-bundle/dashboard-components.js +2953 -2880
  23. package/standalone-bundle/dashboard-components.js.map +1 -1
@@ -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,8 +11,8 @@ import {
11
11
  type MutationFilter,
12
12
  } from './getFilteredMutationsOverTimeData';
13
13
  import { type MutationOverTimeWorkerResponse } from './mutationOverTimeWorker';
14
- import MutationsOverTimeGrid from './mutations-over-time-grid';
15
- import { type MutationOverTimeQuery } from '../../query/queryMutationsOverTime';
14
+ import MutationsOverTimeGrid, { customColumnSchema } from './mutations-over-time-grid';
15
+ import { getProportion, type MutationOverTimeQuery } from '../../query/queryMutationsOverTime';
16
16
  import {
17
17
  lapisFilterSchema,
18
18
  sequenceTypeSchema,
@@ -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
  ),
@@ -354,7 +356,7 @@ function getDownloadData(filteredData: MutationOverTimeDataMap) {
354
356
  return dates.reduce(
355
357
  (accumulated, date) => {
356
358
  const value = filteredData.get(mutation, date);
357
- const proportion = value?.type === 'value' || value?.type === 'wastewaterValue' ? value.proportion : '';
359
+ const proportion = getProportion(value ?? null) ?? '';
358
360
  return {
359
361
  ...accumulated,
360
362
  [date.dateString]: proportion,
@@ -49,6 +49,12 @@ export type MutationOverTimeMutationValue =
49
49
  count: number;
50
50
  totalCount: number;
51
51
  }
52
+ | {
53
+ type: 'valueWithCoverage';
54
+ count: number;
55
+ coverage: number;
56
+ totalCount: number;
57
+ }
52
58
  | {
53
59
  type: 'wastewaterValue';
54
60
  proportion: number;
@@ -59,6 +65,19 @@ export type MutationOverTimeMutationValue =
59
65
  }
60
66
  | null;
61
67
 
68
+ export function getProportion(value: MutationOverTimeMutationValue) {
69
+ switch (value?.type) {
70
+ case 'value':
71
+ case 'wastewaterValue':
72
+ return value.proportion;
73
+ case 'valueWithCoverage':
74
+ return value.count / value.coverage;
75
+ case 'belowThreshold':
76
+ return undefined;
77
+ }
78
+ return undefined;
79
+ }
80
+
62
81
  const MAX_NUMBER_OF_GRID_COLUMNS = 200;
63
82
  export const MUTATIONS_OVER_TIME_MIN_PROPORTION = 0.001;
64
83
 
@@ -316,9 +335,9 @@ async function queryMutationsOverTimeDataDirectEndpoint(
316
335
  return [
317
336
  date.dateString,
318
337
  {
319
- type: 'value',
320
- proportion: count / coverage,
338
+ type: 'valueWithCoverage',
321
339
  count,
340
+ coverage,
322
341
  totalCount,
323
342
  },
324
343
  ];
@@ -120,14 +120,14 @@ describe('queryMutationsOverTimeNewEndpoint', () => {
120
120
 
121
121
  const expectedData = [
122
122
  [
123
- { type: 'value', proportion: 0.4, count: 4, totalCount: 11 },
124
- { type: 'value', proportion: 0, count: 0, totalCount: 12 },
125
- { type: 'value', proportion: 0, count: 0, totalCount: 13 },
123
+ { type: 'valueWithCoverage', count: 4, coverage: 10, totalCount: 11 },
124
+ { type: 'valueWithCoverage', count: 0, coverage: 10, totalCount: 12 },
125
+ { type: 'valueWithCoverage', count: 0, coverage: 10, totalCount: 13 },
126
126
  ],
127
127
  [
128
- { type: 'value', proportion: 0.1, count: 1, totalCount: 11 },
129
- { type: 'value', proportion: 0.2, count: 2, totalCount: 12 },
130
- { type: 'value', proportion: 0.3, count: 3, totalCount: 13 },
128
+ { type: 'valueWithCoverage', count: 1, coverage: 10, totalCount: 11 },
129
+ { type: 'valueWithCoverage', count: 2, coverage: 10, totalCount: 12 },
130
+ { type: 'valueWithCoverage', count: 3, coverage: 10, totalCount: 13 },
131
131
  ],
132
132
  ];
133
133
  expect(mutationOverTimeData.getAsArray()).to.deep.equal(expectedData);
@@ -289,14 +289,14 @@ describe('queryMutationsOverTimeNewEndpoint', () => {
289
289
 
290
290
  expect(mutationOverTimeData.getAsArray()).to.deep.equal([
291
291
  [
292
- { type: 'value', proportion: 0.4, count: 4, totalCount: 11 },
292
+ { type: 'valueWithCoverage', count: 4, coverage: 10, totalCount: 11 },
293
293
  null,
294
- { type: 'value', proportion: 0, count: 0, totalCount: 13 },
294
+ { type: 'valueWithCoverage', count: 0, coverage: 10, totalCount: 13 },
295
295
  ],
296
296
  [
297
- { type: 'value', proportion: 0.1, count: 1, totalCount: 11 },
297
+ { type: 'valueWithCoverage', count: 1, coverage: 10, totalCount: 11 },
298
298
  null,
299
- { type: 'value', proportion: 0.3, count: 3, totalCount: 13 },
299
+ { type: 'valueWithCoverage', count: 3, coverage: 10, totalCount: 13 },
300
300
  ],
301
301
  ]);
302
302
 
@@ -523,8 +523,8 @@ describe('queryMutationsOverTimeNewEndpoint', () => {
523
523
 
524
524
  expect(mutationOverTimeData.getAsArray()).to.deep.equal([
525
525
  [
526
- { type: 'value', proportion: 0.2, count: 2, totalCount: 11 },
527
- { type: 'value', proportion: 0.3, count: 3, totalCount: 12 },
526
+ { type: 'valueWithCoverage', count: 2, coverage: 10, totalCount: 11 },
527
+ { type: 'valueWithCoverage', count: 3, coverage: 10, totalCount: 12 },
528
528
  ],
529
529
  ]);
530
530
 
@@ -635,8 +635,8 @@ describe('queryMutationsOverTimeNewEndpoint', () => {
635
635
 
636
636
  expect(mutationOverTimeData.getAsArray()).to.deep.equal([
637
637
  [
638
- { type: 'value', proportion: 0.1, count: 1, totalCount: 11 },
639
- { type: 'value', proportion: 0.2, count: 2, totalCount: 12 },
638
+ { type: 'valueWithCoverage', count: 1, coverage: 10, totalCount: 11 },
639
+ { type: 'valueWithCoverage', count: 2, coverage: 10, totalCount: 12 },
640
640
  ],
641
641
  ]);
642
642
 
@@ -726,7 +726,7 @@ describe('queryMutationsOverTimeNewEndpoint', () => {
726
726
  });
727
727
 
728
728
  expect(mutationOverTimeData.getAsArray()).to.deep.equal([
729
- [{ type: 'value', proportion: 0.2, count: 2, totalCount: 11 }],
729
+ [{ type: 'valueWithCoverage', count: 2, coverage: 10, totalCount: 11 }],
730
730
  ]);
731
731
 
732
732
  const sequences = mutationOverTimeData.getFirstAxisKeys();
@@ -839,12 +839,12 @@ describe('queryMutationsOverTimeNewEndpoint', () => {
839
839
 
840
840
  expect(mutationOverTimeData.getAsArray()).to.deep.equal([
841
841
  [
842
- { type: 'value', proportion: 0.2, count: 2, totalCount: 11 },
843
- { type: 'value', proportion: 0.3, count: 3, totalCount: 12 },
842
+ { type: 'valueWithCoverage', count: 2, coverage: 10, totalCount: 11 },
843
+ { type: 'valueWithCoverage', count: 3, coverage: 10, totalCount: 12 },
844
844
  ],
845
845
  [
846
- { type: 'value', proportion: 0.4, count: 4, totalCount: 11 },
847
- { type: 'value', proportion: 0.5, count: 5, totalCount: 12 },
846
+ { type: 'valueWithCoverage', count: 4, coverage: 10, totalCount: 11 },
847
+ { type: 'valueWithCoverage', count: 5, coverage: 10, totalCount: 12 },
848
848
  ],
849
849
  ]);
850
850
 
@@ -1017,8 +1017,8 @@ describe('queryMutationsOverTimeNewEndpoint', () => {
1017
1017
  { type: 'belowThreshold', totalCount: 12 },
1018
1018
  ],
1019
1019
  [
1020
- { type: 'value', proportion: 0.2, count: 2, totalCount: 11 },
1021
- { type: 'value', proportion: 0.3, count: 3, totalCount: 12 },
1020
+ { type: 'valueWithCoverage', count: 2, coverage: 10, totalCount: 11 },
1021
+ { type: 'valueWithCoverage', count: 3, coverage: 10, totalCount: 12 },
1022
1022
  ],
1023
1023
  ]);
1024
1024
 
@@ -1139,8 +1139,8 @@ describe('queryMutationsOverTimeNewEndpoint', () => {
1139
1139
  { type: 'belowThreshold', totalCount: 12 },
1140
1140
  ],
1141
1141
  [
1142
- { type: 'value', proportion: 0.2, count: 2, totalCount: 11 },
1143
- { type: 'value', proportion: 0.3, count: 3, totalCount: 12 },
1142
+ { type: 'valueWithCoverage', count: 2, coverage: 10, totalCount: 11 },
1143
+ { type: 'valueWithCoverage', count: 3, coverage: 10, totalCount: 12 },
1144
1144
  ],
1145
1145
  ]);
1146
1146
 
@@ -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';
@@ -17,7 +17,7 @@ import { withinShadowRoot } from '../withinShadowRoot.story';
17
17
  const codeExample = String.raw`
18
18
  <gs-lineage-filter
19
19
  lapisField="pangoLineage"
20
- lapisFilter='{"counrty": "Germany"}'
20
+ lapisFilter='{"country": "Germany"}'
21
21
  placeholderText="Enter lineage"
22
22
  value="B.1.1.7"
23
23
  width="50%">
@@ -163,6 +163,7 @@ export const LineageFilterStringValue: StoryObj<Required<LineageFilterProps>> =
163
163
  lapisField="pangoLineage"
164
164
  placeholderText="Enter a lineage"
165
165
  value="B.1.1.7"
166
+ .lapisFilter=${args.lapisFilter}
166
167
  .multiSelect=${args.multiSelect}
167
168
  ></gs-lineage-filter>
168
169
  </div>
@@ -170,6 +171,9 @@ export const LineageFilterStringValue: StoryObj<Required<LineageFilterProps>> =
170
171
  },
171
172
  args: {
172
173
  multiSelect: false,
174
+ lapisFilter: {
175
+ country: 'Germany',
176
+ },
173
177
  },
174
178
  play: async ({ canvasElement }) => {
175
179
  const canvas = await withinShadowRoot(canvasElement, 'gs-lineage-filter');
@@ -187,6 +191,7 @@ export const LineageFilterArrayValue: StoryObj<Required<LineageFilterProps>> = {
187
191
  lapisField="pangoLineage"
188
192
  placeholderText="Enter a lineage"
189
193
  value='["B.1.1.7", "B.1.1.10"]'
194
+ .lapisFilter=${args.lapisFilter}
190
195
  .multiSelect=${args.multiSelect}
191
196
  ></gs-lineage-filter>
192
197
  </div>
@@ -194,6 +199,9 @@ export const LineageFilterArrayValue: StoryObj<Required<LineageFilterProps>> = {
194
199
  },
195
200
  args: {
196
201
  multiSelect: true,
202
+ lapisFilter: {
203
+ country: 'Germany',
204
+ },
197
205
  },
198
206
  play: async ({ canvasElement }) => {
199
207
  const canvas = await withinShadowRoot(canvasElement, 'gs-lineage-filter');
@@ -38,5 +38,8 @@ describe('gs-mutations-over-time', () => {
38
38
  expectTypeOf<typeof MutationsOverTimeComponent.prototype.pageSizes>().toEqualTypeOf<
39
39
  MutationsOverTimeProps['pageSizes']
40
40
  >();
41
+ expectTypeOf<typeof MutationsOverTimeComponent.prototype.customColumns>().toEqualTypeOf<
42
+ MutationsOverTimeProps['customColumns']
43
+ >();
41
44
  });
42
45
  });
@@ -45,6 +45,7 @@ const meta: Meta<Required<MutationsOverTimeProps>> = {
45
45
  hideGaps: { control: 'boolean' },
46
46
  useNewEndpoint: { control: 'boolean' },
47
47
  pageSizes: { control: 'object' },
48
+ customColumns: { control: 'object' },
48
49
  },
49
50
  args: {
50
51
  lapisFilter: { pangoLineage: 'JN.1*', dateFrom: '2024-01-15', dateTo: '2024-07-10' },
@@ -116,6 +117,7 @@ const Template: StoryObj<Required<MutationsOverTimeProps>> = {
116
117
  .hideGaps=${args.hideGaps}
117
118
  .pageSizes=${args.pageSizes}
118
119
  .useNewEndpoint=${args.useNewEndpoint}
120
+ .customColumns=${args.customColumns}
119
121
  ></gs-mutations-over-time>
120
122
  </gs-app>
121
123
  `,
@@ -183,3 +185,22 @@ export const WithFixedHeight: StoryObj<Required<MutationsOverTimeProps>> = {
183
185
  height: '700px',
184
186
  },
185
187
  };
188
+
189
+ // This test uses mock data: withDisplayMutations.ts (through mutationOverTimeWorker.mock.ts)
190
+ export const WithCustomColumns: StoryObj<Required<MutationsOverTimeProps>> = {
191
+ ...Template,
192
+ args: {
193
+ ...Template.args,
194
+ displayMutations: ['A19722G', 'G21641T', 'T21653-'],
195
+ customColumns: [
196
+ {
197
+ header: 'Jaccard Index',
198
+ values: {
199
+ A19722G: 0.75,
200
+ G21641T: 0.92,
201
+ 'T21653-': 0.58,
202
+ },
203
+ },
204
+ ],
205
+ },
206
+ };
@@ -136,6 +136,13 @@ export class MutationsOverTimeComponent extends PreactLitAdapterWithGridJsStyles
136
136
  @property({ type: Array })
137
137
  pageSizes: number[] | number = [10, 20, 30, 40, 50];
138
138
 
139
+ /**
140
+ * Custom columns to add to the grid.
141
+ * Each column has a header and a map of mutation codes to values.
142
+ */
143
+ @property({ type: Array })
144
+ customColumns?: { header: string; values: Record<string, string | number> }[] = undefined;
145
+
139
146
  /**
140
147
  * @internal
141
148
  */
@@ -165,6 +172,7 @@ export class MutationsOverTimeComponent extends PreactLitAdapterWithGridJsStyles
165
172
  hideGaps={this.hideGaps}
166
173
  useNewEndpoint={this.useNewEndpoint}
167
174
  pageSizes={this.pageSizes}
175
+ customColumns={this.customColumns}
168
176
  />
169
177
  </MutationLinkTemplateContextProvider>
170
178
  </MutationAnnotationsContextProvider>