@genspectrum/dashboard-components 0.3.1 → 0.3.2

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.
@@ -529,6 +529,14 @@ export declare class PrevalenceOverTimeComponent extends PreactLitAdapterWithGri
529
529
  * Visit https://genspectrum.github.io/dashboards/?path=/docs/components-size-of-components--docs for more information.
530
530
  */
531
531
  height: string;
532
+ /**
533
+ * Required.
534
+ *
535
+ * The LAPIS field that the data should be aggregated by.
536
+ * The values will be used on the x-axis of the diagram.
537
+ * Must be a field of type `date` in LAPIS.
538
+ */
539
+ lapisDateField: string;
532
540
  render(): JSX_2.Element;
533
541
  }
534
542
 
@@ -639,6 +647,14 @@ export declare class RelativeGrowthAdvantageComponent extends PreactLitAdapter {
639
647
  * Visit https://genspectrum.github.io/dashboards/?path=/docs/components-size-of-components--docs for more information.
640
648
  */
641
649
  height: string;
650
+ /**
651
+ * Required.
652
+ *
653
+ * The LAPIS field that the data should be aggregated by.
654
+ * The values will be used on the x-axis of the diagram.
655
+ * Must be a field of type `date` in LAPIS.
656
+ */
657
+ lapisDateField: string;
642
658
  render(): JSX_2.Element;
643
659
  }
644
660
 
@@ -732,20 +748,20 @@ declare global {
732
748
 
733
749
  declare global {
734
750
  interface HTMLElementTagNameMap {
735
- 'gs-location-filter': LocationFilterComponent;
751
+ 'gs-date-range-selector': DateRangeSelectorComponent;
736
752
  }
737
753
  interface HTMLElementEventMap {
738
- 'gs-location-changed': CustomEvent<Record<string, string>>;
754
+ 'gs-date-range-changed': CustomEvent<Record<string, string>>;
739
755
  }
740
756
  }
741
757
 
742
758
 
743
759
  declare global {
744
760
  interface HTMLElementTagNameMap {
745
- 'gs-date-range-selector': DateRangeSelectorComponent;
761
+ 'gs-location-filter': LocationFilterComponent;
746
762
  }
747
763
  interface HTMLElementEventMap {
748
- 'gs-date-range-changed': CustomEvent<Record<string, string>>;
764
+ 'gs-location-changed': CustomEvent<Record<string, string>>;
749
765
  }
750
766
  }
751
767
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genspectrum/dashboard-components",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "GenSpectrum web components for building dashboards",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-only",
@@ -0,0 +1,28 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { MockOperator } from './MockOperator';
4
+ import { RenameFieldOperator } from './RenameFieldOperator';
5
+
6
+ const mockOperator = new MockOperator([
7
+ { id: 1, value: 1 },
8
+ { id: 2, value: 2 },
9
+ { id: 3, value: 3 },
10
+ { id: 4, value: 4 },
11
+ { id: 5, value: 5 },
12
+ ]);
13
+
14
+ describe('RenameFieldOperator', () => {
15
+ it('should map the keys', async () => {
16
+ const underTest = new RenameFieldOperator(mockOperator, 'value', 'mappedValue');
17
+
18
+ const result = await underTest.evaluate('lapis');
19
+
20
+ expect(result.content).deep.equal([
21
+ { id: 1, value: 1, mappedValue: 1 },
22
+ { id: 2, value: 2, mappedValue: 2 },
23
+ { id: 3, value: 3, mappedValue: 3 },
24
+ { id: 4, value: 4, mappedValue: 4 },
25
+ { id: 5, value: 5, mappedValue: 5 },
26
+ ]);
27
+ });
28
+ });
@@ -0,0 +1,19 @@
1
+ import { MapOperator } from './MapOperator';
2
+ import type { Operator } from './Operator';
3
+
4
+ export class RenameFieldOperator<
5
+ OldFieldName extends string,
6
+ NewFieldName extends string,
7
+ Data extends { [key in OldFieldName]: unknown },
8
+ > extends MapOperator<Data, Data & { [key in NewFieldName]: Data[OldFieldName] }> {
9
+ constructor(child: Operator<Data>, oldFieldName: OldFieldName, newFieldName: NewFieldName) {
10
+ super(
11
+ child,
12
+ (value) =>
13
+ ({
14
+ ...value,
15
+ [newFieldName]: value[oldFieldName],
16
+ }) as Data & { [key in NewFieldName]: Data[OldFieldName] },
17
+ );
18
+ }
19
+ }
@@ -48,6 +48,7 @@ const Template = {
48
48
  width={args.width}
49
49
  height={args.height}
50
50
  headline={args.headline}
51
+ lapisDateField={args.lapisDateField}
51
52
  />
52
53
  </LapisUrlContext.Provider>
53
54
  ),
@@ -68,6 +69,7 @@ export const TwoVariants = {
68
69
  width: '100%',
69
70
  height: '700px',
70
71
  headline: 'Prevalence over time',
72
+ lapisDateField: 'date',
71
73
  },
72
74
  parameters: {
73
75
  fetchMock: {
@@ -139,6 +141,7 @@ export const OneVariant = {
139
141
  width: '100%',
140
142
  height: '700px',
141
143
  headline: 'Prevalence over time',
144
+ lapisDateField: 'date',
142
145
  },
143
146
  parameters: {
144
147
  fetchMock: {
@@ -39,6 +39,7 @@ export interface PrevalenceOverTimeInnerProps {
39
39
  smoothingWindow: number;
40
40
  views: View[];
41
41
  confidenceIntervalMethods: ConfidenceIntervalMethod[];
42
+ lapisDateField: string;
42
43
  }
43
44
 
44
45
  export const PrevalenceOverTime: FunctionComponent<PrevalenceOverTimeProps> = ({
@@ -51,6 +52,7 @@ export const PrevalenceOverTime: FunctionComponent<PrevalenceOverTimeProps> = ({
51
52
  width,
52
53
  height,
53
54
  headline = 'Prevalence over time',
55
+ lapisDateField,
54
56
  }) => {
55
57
  const size = { height, width };
56
58
 
@@ -65,6 +67,7 @@ export const PrevalenceOverTime: FunctionComponent<PrevalenceOverTimeProps> = ({
65
67
  smoothingWindow={smoothingWindow}
66
68
  views={views}
67
69
  confidenceIntervalMethods={confidenceIntervalMethods}
70
+ lapisDateField={lapisDateField}
68
71
  />
69
72
  </Headline>
70
73
  </ResizeContainer>
@@ -79,11 +82,12 @@ export const PrevalenceOverTimeInner: FunctionComponent<PrevalenceOverTimeInnerP
79
82
  smoothingWindow,
80
83
  views,
81
84
  confidenceIntervalMethods,
85
+ lapisDateField,
82
86
  }) => {
83
87
  const lapis = useContext(LapisUrlContext);
84
88
 
85
89
  const { data, error, isLoading } = useQuery(
86
- () => queryPrevalenceOverTime(numerator, denominator, granularity, smoothingWindow, lapis),
90
+ () => queryPrevalenceOverTime(numerator, denominator, granularity, smoothingWindow, lapis, lapisDateField),
87
91
  [lapis, numerator, denominator, granularity, smoothingWindow],
88
92
  );
89
93
 
@@ -35,6 +35,7 @@ export const Primary = {
35
35
  width={args.width}
36
36
  height={args.height}
37
37
  headline={args.headline}
38
+ lapisDateField={args.lapisDateField}
38
39
  />
39
40
  </LapisUrlContext.Provider>
40
41
  ),
@@ -46,6 +47,7 @@ export const Primary = {
46
47
  width: '100%',
47
48
  height: '700px',
48
49
  headline: 'Relative growth advantage',
50
+ lapisDateField: 'date',
49
51
  },
50
52
  parameters: {
51
53
  fetchMock: {
@@ -33,6 +33,7 @@ export interface RelativeGrowthAdvantagePropsInner {
33
33
  denominator: LapisFilter;
34
34
  generationTime: number;
35
35
  views: View[];
36
+ lapisDateField: string;
36
37
  }
37
38
 
38
39
  export const RelativeGrowthAdvantage: FunctionComponent<RelativeGrowthAdvantageProps> = ({
@@ -43,6 +44,7 @@ export const RelativeGrowthAdvantage: FunctionComponent<RelativeGrowthAdvantageP
43
44
  denominator,
44
45
  generationTime,
45
46
  headline = 'Relative growth advantage',
47
+ lapisDateField,
46
48
  }) => {
47
49
  const size = { height, width };
48
50
 
@@ -55,6 +57,7 @@ export const RelativeGrowthAdvantage: FunctionComponent<RelativeGrowthAdvantageP
55
57
  numerator={numerator}
56
58
  denominator={denominator}
57
59
  generationTime={generationTime}
60
+ lapisDateField={lapisDateField}
58
61
  />
59
62
  </Headline>
60
63
  </ResizeContainer>
@@ -67,12 +70,13 @@ export const RelativeGrowthAdvantageInner: FunctionComponent<RelativeGrowthAdvan
67
70
  denominator,
68
71
  generationTime,
69
72
  views,
73
+ lapisDateField,
70
74
  }) => {
71
75
  const lapis = useContext(LapisUrlContext);
72
76
  const [yAxisScaleType, setYAxisScaleType] = useState<ScaleType>('linear');
73
77
 
74
78
  const { data, error, isLoading } = useQuery(
75
- () => queryRelativeGrowthAdvantage(numerator, denominator, generationTime, lapis),
79
+ () => queryRelativeGrowthAdvantage(numerator, denominator, generationTime, lapis, lapisDateField),
76
80
  [lapis, numerator, denominator, generationTime, views],
77
81
  );
78
82
 
@@ -3,6 +3,7 @@ import { FetchAggregatedOperator } from '../operator/FetchAggregatedOperator';
3
3
  import { FillMissingOperator } from '../operator/FillMissingOperator';
4
4
  import { GroupByAndSumOperator } from '../operator/GroupByAndSumOperator';
5
5
  import { MapOperator } from '../operator/MapOperator';
6
+ import { RenameFieldOperator } from '../operator/RenameFieldOperator';
6
7
  import { SlidingOperator } from '../operator/SlidingOperator';
7
8
  import { SortOperator } from '../operator/SortOperator';
8
9
  import { type LapisFilter, type NamedLapisFilter, type TemporalGranularity } from '../types';
@@ -27,14 +28,15 @@ export function queryPrevalenceOverTime(
27
28
  granularity: TemporalGranularity,
28
29
  smoothingWindow: number,
29
30
  lapis: string,
31
+ lapisDateField: string,
30
32
  signal?: AbortSignal,
31
33
  ): Promise<PrevalenceOverTimeData> {
32
34
  const numeratorFilters = makeArray(numeratorFilter);
33
35
 
34
- const denominatorData = fetchAndPrepare(denominatorFilter, granularity, smoothingWindow);
36
+ const denominatorData = fetchAndPrepare(denominatorFilter, granularity, smoothingWindow, lapisDateField);
35
37
  const subQueries = numeratorFilters.map(async (namedLapisFilter) => {
36
38
  const { displayName, lapisFilter } = namedLapisFilter;
37
- const numeratorData = fetchAndPrepare(lapisFilter, granularity, smoothingWindow);
39
+ const numeratorData = fetchAndPrepare(lapisFilter, granularity, smoothingWindow, lapisDateField);
38
40
  const divide = new DivisionOperator(
39
41
  numeratorData,
40
42
  denominatorData,
@@ -60,11 +62,15 @@ function makeArray<T>(arrayOrSingleItem: T | T[]) {
60
62
  return [arrayOrSingleItem];
61
63
  }
62
64
 
63
- function fetchAndPrepare(filter: LapisFilter, granularity: TemporalGranularity, smoothingWindow: number) {
64
- const fetchData = new FetchAggregatedOperator<{
65
- date: string | null;
66
- }>(filter, ['date']);
67
- const mapData = new MapOperator(fetchData, (d) => mapDateToGranularityRange(d, granularity));
65
+ function fetchAndPrepare<LapisDateField extends string>(
66
+ filter: LapisFilter,
67
+ granularity: TemporalGranularity,
68
+ smoothingWindow: number,
69
+ lapisDateField: LapisDateField,
70
+ ) {
71
+ const fetchData = new FetchAggregatedOperator<{ [key in LapisDateField]: string | null }>(filter, [lapisDateField]);
72
+ const dataWithFixedDateKey = new RenameFieldOperator(fetchData, lapisDateField, 'date');
73
+ const mapData = new MapOperator(dataWithFixedDateKey, (d) => mapDateToGranularityRange(d, granularity));
68
74
  const groupByData = new GroupByAndSumOperator(mapData, 'dateRange', 'count');
69
75
  const fillData = new FillMissingOperator(
70
76
  groupByData,
@@ -1,25 +1,29 @@
1
1
  import { FetchAggregatedOperator } from '../operator/FetchAggregatedOperator';
2
2
  import { MapOperator } from '../operator/MapOperator';
3
+ import { RenameFieldOperator } from '../operator/RenameFieldOperator';
3
4
  import { type LapisFilter } from '../types';
4
5
  import { getMinMaxTemporal, TemporalCache, type YearMonthDay } from '../utils/temporal';
5
6
 
6
7
  export type RelativeGrowthAdvantageData = Awaited<ReturnType<typeof queryRelativeGrowthAdvantage>>;
7
8
 
8
- export async function queryRelativeGrowthAdvantage(
9
+ export async function queryRelativeGrowthAdvantage<LapisDateField extends string>(
9
10
  numerator: LapisFilter,
10
11
  denominator: LapisFilter,
11
12
  generationTime: number,
12
13
  lapis: string,
14
+ lapisDateField: LapisDateField,
13
15
  signal?: AbortSignal,
14
16
  ) {
15
17
  const fetchNumerator = new FetchAggregatedOperator<{
16
- date: string | null;
17
- }>(numerator, ['date']);
18
+ [key in LapisDateField]: string | null;
19
+ }>(numerator, [lapisDateField]);
18
20
  const fetchDenominator = new FetchAggregatedOperator<{
19
- date: string | null;
20
- }>(denominator, ['date']);
21
- const mapNumerator = new MapOperator(fetchNumerator, toYearMonthDay);
22
- const mapDenominator = new MapOperator(fetchDenominator, toYearMonthDay);
21
+ [key in LapisDateField]: string | null;
22
+ }>(denominator, [lapisDateField]);
23
+ const mapToFixedDateKeyNumerator = new RenameFieldOperator(fetchNumerator, lapisDateField, 'date');
24
+ const mapToFixedDateKeyDenominator = new RenameFieldOperator(fetchDenominator, lapisDateField, 'date');
25
+ const mapNumerator = new MapOperator(mapToFixedDateKeyNumerator, toYearMonthDay);
26
+ const mapDenominator = new MapOperator(mapToFixedDateKeyDenominator, toYearMonthDay);
23
27
  const [numeratorData, denominatorData] = await Promise.all([
24
28
  mapNumerator.evaluate(lapis, signal),
25
29
  mapDenominator.evaluate(lapis, signal),
@@ -23,8 +23,9 @@ const codeExample = String.raw`
23
23
  views='["bar", "line", "bubble", "table"]'
24
24
  confidenceIntervalMethods='["wilson"]'
25
25
  headline="Prevalence over time"
26
- width='100%'
27
- height='700px'
26
+ width="100%"
27
+ height="700px"
28
+ lapisDateField="date"
28
29
  ></gs-prevalence-over-time>`;
29
30
 
30
31
  const meta: Meta<Required<PrevalenceOverTimeProps>> = {
@@ -75,6 +76,7 @@ const Template: StoryObj<Required<PrevalenceOverTimeProps>> = {
75
76
  .width=${args.width}
76
77
  .height=${args.height}
77
78
  .headline=${args.headline}
79
+ .lapisDateField=${args.lapisDateField}
78
80
  ></gs-prevalence-over-time>
79
81
  </gs-app>
80
82
  `,
@@ -95,6 +97,7 @@ export const TwoVariants: StoryObj<Required<PrevalenceOverTimeProps>> = {
95
97
  width: '100%',
96
98
  height: '700px',
97
99
  headline: 'Prevalence over time',
100
+ lapisDateField: 'date',
98
101
  },
99
102
  parameters: {
100
103
  fetchMock: {
@@ -166,6 +169,7 @@ export const OneVariant: StoryObj<Required<PrevalenceOverTimeProps>> = {
166
169
  width: '100%',
167
170
  height: '700px',
168
171
  headline: 'Prevalence over time',
172
+ lapisDateField: 'date',
169
173
  },
170
174
  parameters: {
171
175
  fetchMock: {
@@ -126,6 +126,16 @@ export class PrevalenceOverTimeComponent extends PreactLitAdapterWithGridJsStyle
126
126
  @property({ type: String })
127
127
  height: string = '700px';
128
128
 
129
+ /**
130
+ * Required.
131
+ *
132
+ * The LAPIS field that the data should be aggregated by.
133
+ * The values will be used on the x-axis of the diagram.
134
+ * Must be a field of type `date` in LAPIS.
135
+ */
136
+ @property({ type: String })
137
+ lapisDateField: string = 'date';
138
+
129
139
  override render() {
130
140
  return (
131
141
  <PrevalenceOverTime
@@ -138,6 +148,7 @@ export class PrevalenceOverTimeComponent extends PreactLitAdapterWithGridJsStyle
138
148
  width={this.width}
139
149
  height={this.height}
140
150
  headline={this.headline}
151
+ lapisDateField={this.lapisDateField}
141
152
  />
142
153
  );
143
154
  }
@@ -18,6 +18,7 @@ const codeExample = String.raw`
18
18
  width='100%'
19
19
  height='700px'
20
20
  headline="Relative growth advantage"
21
+ lapisDateField="date"
21
22
  ></gs-relative-growth-advantage>`;
22
23
 
23
24
  const meta: Meta<RelativeGrowthAdvantageProps> = {
@@ -58,6 +59,7 @@ const Template: StoryObj<Required<RelativeGrowthAdvantageProps>> = {
58
59
  .width=${args.width}
59
60
  .height=${args.height}
60
61
  .headline=${args.headline}
62
+ .lapisDateField=${args.lapisDateField}
61
63
  ></gs-relative-growth-advantage>
62
64
  </gs-app>
63
65
  `,
@@ -73,6 +75,7 @@ export const Default: StoryObj<Required<RelativeGrowthAdvantageProps>> = {
73
75
  width: '100%',
74
76
  height: '700px',
75
77
  headline: 'Relative growth advantage',
78
+ lapisDateField: 'date',
76
79
  },
77
80
  parameters: {
78
81
  fetchMock: {
@@ -84,6 +84,16 @@ export class RelativeGrowthAdvantageComponent extends PreactLitAdapter {
84
84
  @property({ type: String })
85
85
  height: string = '700px';
86
86
 
87
+ /**
88
+ * Required.
89
+ *
90
+ * The LAPIS field that the data should be aggregated by.
91
+ * The values will be used on the x-axis of the diagram.
92
+ * Must be a field of type `date` in LAPIS.
93
+ */
94
+ @property({ type: String })
95
+ lapisDateField: string = 'date';
96
+
87
97
  override render() {
88
98
  return (
89
99
  <RelativeGrowthAdvantage
@@ -94,6 +104,7 @@ export class RelativeGrowthAdvantageComponent extends PreactLitAdapter {
94
104
  width={this.width}
95
105
  height={this.height}
96
106
  headline={this.headline}
107
+ lapisDateField={this.lapisDateField}
97
108
  />
98
109
  );
99
110
  }