diff --git a/packages/app/package.json b/packages/app/package.json
index 95805e8acf..4a1a0a4b9f 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -139,7 +139,7 @@
"bootstrap": "exit 0",
"export": "next export",
"dev": "yarn workspace @corona-dashboard/icons build && yarn workspace @corona-dashboard/common build && run-p dev:common dev:next dev:lokalize",
- "dev:next": "node next-server.js",
+ "dev:next": "cross-env NODE_OPTIONS='--inspect' node next-server.js",
"dev:lokalize": "chokidar \"./src/locale/nl_export.json\" -c \"yarn workspace @corona-dashboard/cms lokalize:generate-types\"",
"dev:common": "yarn workspace @corona-dashboard/common build:watch",
"build": "cross-env NEXT_TELEMETRY_DISABLED=1 && next build",
diff --git a/packages/app/schema/archived_nl/__index.json b/packages/app/schema/archived_nl/__index.json
index 7322cb6794..03e8b825f0 100644
--- a/packages/app/schema/archived_nl/__index.json
+++ b/packages/app/schema/archived_nl/__index.json
@@ -42,6 +42,7 @@
"vaccine_vaccinated_or_support_archived_20230411",
"vaccine_delivery_per_supplier_archived_20211101",
"vaccine_stock_archived_20211024",
+ "variants_archived_20231101",
"tested_ggd_archived_20230321",
"tested_overall_archived_20230331",
"tested_per_age_group_archived_20230331",
@@ -186,6 +187,9 @@
"vaccine_stock_archived_20211024": {
"$ref": "vaccine_stock.json"
},
+ "variants_archived_20231101": {
+ "$ref": "variants.json"
+ },
"repeating_shot_administered_20220713": {
"$ref": "repeating_shot_administered.json"
},
diff --git a/packages/app/schema/archived_nl/variants.json b/packages/app/schema/archived_nl/variants.json
new file mode 100644
index 0000000000..84369cd74a
--- /dev/null
+++ b/packages/app/schema/archived_nl/variants.json
@@ -0,0 +1,86 @@
+{
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "type": "object",
+ "title": "archived_nl_variants",
+ "required": ["values"],
+ "additionalProperties": false,
+ "properties": {
+ "values": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/variant"
+ }
+ }
+ },
+ "definitions": {
+ "variant": {
+ "type": "object",
+ "title": "archived_nl_variants_variant",
+ "additionalProperties": false,
+ "required": ["variant_code", "values", "last_value"],
+ "properties": {
+ "variant_code": {
+ "type": "string"
+ },
+ "values": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/value"
+ }
+ },
+ "last_value": {
+ "$ref": "#/definitions/value"
+ }
+ }
+ },
+ "value": {
+ "type": "object",
+ "title": "archived_nl_variants_variant_value",
+ "additionalProperties": false,
+ "required": [
+ "order",
+ "occurrence",
+ "percentage",
+ "sample_size",
+ "date_start_unix",
+ "date_end_unix",
+ "date_of_report_unix",
+ "date_of_insertion_unix",
+ "label_nl",
+ "label_en"
+ ],
+ "properties": {
+ "order": {
+ "type": "integer"
+ },
+ "occurrence": {
+ "type": "integer"
+ },
+ "percentage": {
+ "type": "number"
+ },
+ "sample_size": {
+ "type": "integer"
+ },
+ "date_start_unix": {
+ "type": "integer"
+ },
+ "date_end_unix": {
+ "type": "integer"
+ },
+ "date_of_insertion_unix": {
+ "type": "integer"
+ },
+ "date_of_report_unix": {
+ "type": "integer"
+ },
+ "label_nl": {
+ "type": "string"
+ },
+ "label_en": {
+ "type": "string"
+ }
+ }
+ }
+ }
+}
diff --git a/packages/app/schema/nl/__index.json b/packages/app/schema/nl/__index.json
index a8ed83c1e1..86c6e693f1 100644
--- a/packages/app/schema/nl/__index.json
+++ b/packages/app/schema/nl/__index.json
@@ -20,7 +20,8 @@
"infectionradar_symptoms_trend_per_age_group_weekly",
"sewer",
"vaccine_campaigns",
- "vaccine_administered_last_timeframe"
+ "vaccine_administered_last_timeframe",
+ "variants"
],
"additionalProperties": false,
"properties": {
diff --git a/packages/app/src/components/kpi/bordered-kpi-section.tsx b/packages/app/src/components/kpi/bordered-kpi-section.tsx
index a5f0d5a9a4..3a254c3162 100644
--- a/packages/app/src/components/kpi/bordered-kpi-section.tsx
+++ b/packages/app/src/components/kpi/bordered-kpi-section.tsx
@@ -9,10 +9,11 @@ import { KpiContent } from './components/kpi-content';
import { BorderedKpiSectionProps } from './types';
import { Markdown } from '../markdown';
-export const BorderedKpiSection = ({ title, description, source, dateOrRange, tilesData }: BorderedKpiSectionProps) => {
+export const BorderedKpiSection = ({ title, description, source, dateOrRange, tilesData, disclaimer }: BorderedKpiSectionProps) => {
const metadata: MetadataProps = {
date: dateOrRange,
source: source,
+ disclaimer: disclaimer,
};
return (
diff --git a/packages/app/src/components/kpi/types.ts b/packages/app/src/components/kpi/types.ts
index 8f68774cb0..986f11d303 100644
--- a/packages/app/src/components/kpi/types.ts
+++ b/packages/app/src/components/kpi/types.ts
@@ -29,6 +29,7 @@ export interface BorderedKpiSectionProps {
};
tilesData: [TileData, TileData];
title: string;
+ disclaimer?: string;
}
type BarType = {
diff --git a/packages/app/src/components/metadata.tsx b/packages/app/src/components/metadata.tsx
index 7717c3d9a4..0df2b1ecee 100644
--- a/packages/app/src/components/metadata.tsx
+++ b/packages/app/src/components/metadata.tsx
@@ -5,6 +5,7 @@ import { space } from '~/style/theme';
import { replaceVariablesInText } from '~/utils/replace-variables-in-text';
import { Box } from './base';
import { InlineText, Text } from './typography';
+import { Markdown } from '~/components/markdown';
type source = {
text: string;
@@ -25,9 +26,10 @@ export interface MetadataProps extends MarginBottomProps {
isTileFooter?: boolean;
datumsText?: string;
intervalCount?: string;
+ disclaimer?: string;
}
-export function Metadata({ date, source, obtainedAt, isTileFooter, datumsText, marginBottom, dataSources, intervalCount }: MetadataProps) {
+export function Metadata({ date, source, obtainedAt, isTileFooter, datumsText, marginBottom, dataSources, intervalCount, disclaimer }: MetadataProps) {
const { commonTexts, formatDateFromSeconds } = useIntl();
const dateString =
@@ -77,6 +79,11 @@ export function Metadata({ date, source, obtainedAt, isTileFooter, datumsText, m
})
) : (
<>
+ {disclaimer && (
+
+
+
+ )}
{dateString}
{obtainedAt &&
` ${replaceVariablesInText(commonTexts.common.metadata.obtained, {
diff --git a/packages/app/src/components/stacked-chart/stacked-chart.tsx b/packages/app/src/components/stacked-chart/stacked-chart.tsx
index 18141ff0df..cbb52b4407 100644
--- a/packages/app/src/components/stacked-chart/stacked-chart.tsx
+++ b/packages/app/src/components/stacked-chart/stacked-chart.tsx
@@ -65,6 +65,7 @@ type StackedChartProps = {
config: Config[];
valueAnnotation?: string;
initialWidth?: number;
+ disableLegend?: boolean;
expectedLabel?: string;
formatTooltip?: TooltipFormatter;
isPercentage?: boolean;
@@ -105,6 +106,7 @@ export function StackedChart(props: StackedChartProp
config,
initialWidth = 840,
isPercentage,
+ disableLegend,
expectedLabel,
formatTickValue: formatYTickValue,
formatTooltip,
@@ -461,9 +463,11 @@ export function StackedChart(props: StackedChartProp
)}
-
-
-
+ {!disableLegend && legendItems && (
+
+
+
+ )}
>
);
}
diff --git a/packages/app/src/domain/vaccine/vaccine-campaigns-tile/vaccine-campaigns-tile.tsx b/packages/app/src/domain/vaccine/vaccine-campaigns-tile/vaccine-campaigns-tile.tsx
index b07689951a..31b96bf566 100644
--- a/packages/app/src/domain/vaccine/vaccine-campaigns-tile/vaccine-campaigns-tile.tsx
+++ b/packages/app/src/domain/vaccine/vaccine-campaigns-tile/vaccine-campaigns-tile.tsx
@@ -1,16 +1,11 @@
import { useBreakpoints } from '~/utils/use-breakpoints';
-import { ChartTile, Markdown, MetadataProps } from '~/components';
-import { Text } from '~/components/typography';
+import { ChartTile, MetadataProps } from '~/components';
import { NarrowVaccineCampaignTable } from './components/narrow-vaccine-campaign-table';
import { WideVaccineCampaignTable } from './components/wide-vaccine-campaign-table';
import { VaccineCampaign, VaccineCampaignDescriptions, VaccineCampaignHeaders, VaccineCampaignOptions } from './types';
-import { Box } from '~/components/base';
-import { space } from '~/style/theme';
-
interface VaccineCampaignsTileProps {
title: string;
description: string;
- descriptionFooter: string;
metadata: MetadataProps;
headers: VaccineCampaignHeaders;
campaigns: VaccineCampaign[];
@@ -18,7 +13,7 @@ interface VaccineCampaignsTileProps {
campaignOptions?: VaccineCampaignOptions;
}
-export const VaccineCampaignsTile = ({ title, headers, campaigns, campaignDescriptions, description, descriptionFooter, metadata, campaignOptions }: VaccineCampaignsTileProps) => {
+export const VaccineCampaignsTile = ({ title, headers, campaigns, campaignDescriptions, description, metadata, campaignOptions }: VaccineCampaignsTileProps) => {
const breakpoints = useBreakpoints();
// Display only the campaigns that are not hidden in the campaignOptions prop
@@ -36,11 +31,6 @@ export const VaccineCampaignsTile = ({ title, headers, campaigns, campaignDescri
) : (
)}
-
-
-
-
-
>
);
diff --git a/packages/app/src/domain/variants/static-props/get-variant-chart-data.ts b/packages/app/src/domain/variants/data-selection/get-archived-variant-chart-data.ts
similarity index 79%
rename from packages/app/src/domain/variants/static-props/get-variant-chart-data.ts
rename to packages/app/src/domain/variants/data-selection/get-archived-variant-chart-data.ts
index 9943c5ef49..586fe5bd97 100644
--- a/packages/app/src/domain/variants/static-props/get-variant-chart-data.ts
+++ b/packages/app/src/domain/variants/data-selection/get-archived-variant-chart-data.ts
@@ -1,16 +1,9 @@
-import { NlVariants } from '@corona-dashboard/common';
+import { ArchivedNlVariants } from '@corona-dashboard/common';
import { isDefined } from 'ts-is-present';
-
-export type VariantCode = string;
-
-export type VariantChartValue = {
- date_start_unix: number;
- date_end_unix: number;
- is_reliable: boolean;
-} & Record;
+import { VariantChartValue } from '~/domain/variants/data-selection/types';
const EMPTY_VALUES = {
- variantChart: null,
+ archivedVariantChart: null,
dates: {
date_of_report_unix: 0,
date_start_unix: 0,
@@ -22,7 +15,7 @@ const EMPTY_VALUES = {
* Returns values for variant timeseries chart
* @param variants
*/
-export function getVariantChartData(variants: NlVariants | undefined) {
+export function getArchivedVariantChartData(variants: ArchivedNlVariants | undefined) {
if (!isDefined(variants) || !isDefined(variants.values)) {
return EMPTY_VALUES;
}
@@ -51,7 +44,7 @@ export function getVariantChartData(variants: NlVariants | undefined) {
});
return {
- variantChart: values,
+ archivedVariantChart: values,
dates: {
date_of_report_unix: firstVariantInList.last_value.date_of_report_unix,
date_start_unix: firstVariantInList.last_value.date_start_unix,
diff --git a/packages/app/src/domain/variants/data-selection/get-variant-bar-chart-data.ts b/packages/app/src/domain/variants/data-selection/get-variant-bar-chart-data.ts
new file mode 100644
index 0000000000..04f31e1605
--- /dev/null
+++ b/packages/app/src/domain/variants/data-selection/get-variant-bar-chart-data.ts
@@ -0,0 +1,54 @@
+import { NlVariants } from '@corona-dashboard/common';
+import { isDefined } from 'ts-is-present';
+import { VariantChartValue } from '~/domain/variants/data-selection/types';
+
+const EMPTY_VALUES = {
+ variantChart: null,
+ dates: {
+ date_of_report_unix: 0,
+ date_start_unix: 0,
+ date_end_unix: 0,
+ },
+} as const;
+
+/**
+ * Returns values for variant timeseries chart
+ * @param variants
+ */
+export function getVariantBarChartData(variants: NlVariants) {
+ if (!isDefined(variants) || !isDefined(variants.values)) {
+ return EMPTY_VALUES;
+ }
+
+ const sortedVariants = variants.values.sort((a, b) => b.last_value.order - a.last_value.order);
+
+ const firstVariantInList = sortedVariants.shift();
+
+ if (!isDefined(firstVariantInList)) {
+ return EMPTY_VALUES;
+ }
+
+ const values = firstVariantInList.values.map((value, index) => {
+ const item = {
+ is_reliable: true,
+ date_start_unix: value.date_start_unix,
+ date_end_unix: value.date_end_unix,
+ [`${firstVariantInList.variant_code}_occurrence`]: value.occurrence,
+ } as VariantChartValue;
+
+ sortedVariants.forEach((variant) => {
+ (item as unknown as Record)[`${variant.variant_code}_occurrence`] = variant.values[index].occurrence;
+ });
+
+ return item;
+ });
+
+ return {
+ variantChart: values,
+ dates: {
+ date_of_report_unix: firstVariantInList.last_value.date_of_report_unix,
+ date_start_unix: firstVariantInList.last_value.date_start_unix,
+ date_end_unix: firstVariantInList.last_value.date_end_unix,
+ },
+ } as const;
+}
diff --git a/packages/app/src/domain/variants/static-props/get-variant-order-colors.ts b/packages/app/src/domain/variants/data-selection/get-variant-order-colors.ts
similarity index 88%
rename from packages/app/src/domain/variants/static-props/get-variant-order-colors.ts
rename to packages/app/src/domain/variants/data-selection/get-variant-order-colors.ts
index b100539ff3..42c0fd5b15 100644
--- a/packages/app/src/domain/variants/static-props/get-variant-order-colors.ts
+++ b/packages/app/src/domain/variants/data-selection/get-variant-order-colors.ts
@@ -1,11 +1,6 @@
import { NlVariants, colors } from '@corona-dashboard/common';
import { isDefined } from 'ts-is-present';
-import { VariantCode } from './';
-
-export type ColorMatch = {
- variant: VariantCode;
- color: string;
-};
+import { ColorMatch, VariantCode } from '~/domain/variants/data-selection/types';
const getColorForVariant = (variantCode: VariantCode, index: number): string => {
if (variantCode === 'other_variants') return colors.gray5;
diff --git a/packages/app/src/domain/variants/static-props/get-variant-table-data.ts b/packages/app/src/domain/variants/data-selection/get-variant-table-data.ts
similarity index 83%
rename from packages/app/src/domain/variants/static-props/get-variant-table-data.ts
rename to packages/app/src/domain/variants/data-selection/get-variant-table-data.ts
index de5947f6ed..cacdbfac67 100644
--- a/packages/app/src/domain/variants/static-props/get-variant-table-data.ts
+++ b/packages/app/src/domain/variants/data-selection/get-variant-table-data.ts
@@ -1,18 +1,7 @@
-import { colors, NlNamedDifference, NlVariants, NlVariantsVariant, NamedDifferenceDecimal } from '@corona-dashboard/common';
+import { colors, NlNamedDifference, NlVariants, NlVariantsVariant } from '@corona-dashboard/common';
import { first } from 'lodash';
import { isDefined } from 'ts-is-present';
-import { ColorMatch } from './get-variant-order-colors';
-import { VariantCode } from '../static-props';
-
-export type VariantRow = {
- variantCode: VariantCode;
- order: number;
- percentage: number | null;
- difference?: NamedDifferenceDecimal | null;
- color: string;
-};
-
-export type VariantTableData = ReturnType;
+import { ColorMatch, VariantRow } from '~/domain/variants/data-selection/types';
/**
* Return values to populate the variants table
diff --git a/packages/app/src/domain/variants/data-selection/index.ts b/packages/app/src/domain/variants/data-selection/index.ts
new file mode 100644
index 0000000000..7589614493
--- /dev/null
+++ b/packages/app/src/domain/variants/data-selection/index.ts
@@ -0,0 +1,4 @@
+export * from './get-variant-bar-chart-data';
+export * from './get-archived-variant-chart-data';
+export * from './get-variant-order-colors';
+export * from './get-variant-table-data';
diff --git a/packages/app/src/domain/variants/data-selection/types.ts b/packages/app/src/domain/variants/data-selection/types.ts
new file mode 100644
index 0000000000..682bbc6651
--- /dev/null
+++ b/packages/app/src/domain/variants/data-selection/types.ts
@@ -0,0 +1,40 @@
+import { SiteText } from '~/locale';
+import { NamedDifferenceDecimal, TimestampedValue } from '@corona-dashboard/common';
+import { getVariantTableData } from '~/domain/variants/data-selection/get-variant-table-data';
+
+export type VariantCode = string;
+
+export type ColorMatch = {
+ variant: VariantCode;
+ color: string;
+};
+
+export type VariantTableData = ReturnType;
+
+export type VariantChartValue = {
+ date_start_unix: number;
+ date_end_unix: number;
+ is_reliable: boolean;
+} & Record;
+
+export type VariantRow = {
+ variantCode: VariantCode;
+ order: number;
+ percentage: number | null;
+ difference?: NamedDifferenceDecimal | null;
+ color: string;
+};
+
+export type VariantDynamicLabels = Record;
+
+export type VariantsOverTimeGraphText = SiteText['pages']['variants_page']['nl']['varianten_over_tijd_grafiek'];
+
+export type VariantsStackedAreaTileText = {
+ variantCodes: VariantDynamicLabels;
+} & SiteText['pages']['variants_page']['nl']['varianten_over_tijd_grafiek'];
+
+export type StackedBarConfig = {
+ metricProperty: keyof T;
+ label: string;
+ color: string;
+};
diff --git a/packages/app/src/domain/variants/index.ts b/packages/app/src/domain/variants/index.ts
new file mode 100644
index 0000000000..270d708310
--- /dev/null
+++ b/packages/app/src/domain/variants/index.ts
@@ -0,0 +1,4 @@
+export * from './variants-stacked-bar-chart-tile';
+export * from './variants-stacked-area-tile';
+export * from './variants-table-tile';
+export * from './data-selection';
diff --git a/packages/app/src/domain/variants/logic/use-bar-config.ts b/packages/app/src/domain/variants/logic/use-bar-config.ts
new file mode 100644
index 0000000000..9de1371762
--- /dev/null
+++ b/packages/app/src/domain/variants/logic/use-bar-config.ts
@@ -0,0 +1,69 @@
+import { ColorMatch, VariantChartValue, StackedBarConfig, VariantDynamicLabels, VariantsOverTimeGraphText } from '~/domain/variants/data-selection/types';
+import { useMemo } from 'react';
+import { getValuesInTimeframe, TimeframeOption } from '@corona-dashboard/common';
+import { isPresent } from 'ts-is-present';
+
+const extractVariantNamesFromValues = (values: VariantChartValue[]) => {
+ return values
+ .flatMap((variantChartValue) => Object.keys(variantChartValue))
+ .filter((keyName, index, array) => array.indexOf(keyName) === index)
+ .filter((keyName) => keyName.endsWith('_occurrence'));
+};
+
+export const useBarConfig = (
+ values: VariantChartValue[],
+ selectedOptions: (keyof VariantChartValue)[],
+ variantLabels: VariantDynamicLabels,
+ tooltipLabels: VariantsOverTimeGraphText,
+ colors: ColorMatch[],
+ timeframe: TimeframeOption,
+ today: Date
+) => {
+ return useMemo(() => {
+ const valuesInTimeframe: VariantChartValue[] = getValuesInTimeframe(values, timeframe, today);
+
+ const activeVariantsInTimeframeValues: VariantChartValue[] = valuesInTimeframe.map((val) => {
+ return Object.fromEntries(
+ Object.entries(val).filter(([key, value]) => (key.includes('occurrence') ? value !== 0 && isPresent(value) && !isNaN(Number(value)) : value))
+ ) as VariantChartValue;
+ });
+
+ const activeVariantsInTimeframeNames: string[] = extractVariantNamesFromValues(activeVariantsInTimeframeValues);
+
+ const listOfVariantCodes: string[] = extractVariantNamesFromValues(valuesInTimeframe)
+ .filter((keyName) => activeVariantsInTimeframeNames.includes(keyName))
+ .reverse();
+
+ const barChartConfig: StackedBarConfig[] = [];
+
+ listOfVariantCodes.forEach((variantKey) => {
+ const variantCodeName = variantKey.split('_').slice(0, -1).join('_');
+
+ const variantMetricPropertyName = variantCodeName.concat('_occurrence');
+
+ const variantDynamicLabel = variantLabels[variantCodeName];
+
+ const color = colors.find((variantColors) => variantColors.variant === variantCodeName)?.color;
+
+ if (variantDynamicLabel) {
+ const barChartConfigEntry = {
+ metricProperty: variantMetricPropertyName,
+ color: color,
+ label: variantDynamicLabel,
+ shape: 'gapped-area',
+ };
+
+ barChartConfig.push(barChartConfigEntry as StackedBarConfig);
+ }
+ });
+
+ const selectOptions: StackedBarConfig[] = [...barChartConfig];
+
+ if (selectedOptions.length > 0) {
+ const selection = barChartConfig.filter((selectedConfig) => selectedOptions.includes(selectedConfig.metricProperty));
+ return [selection, selectOptions];
+ } else {
+ return [barChartConfig, selectOptions];
+ }
+ }, [values, tooltipLabels.tooltip_labels.other_percentage, variantLabels, colors, selectedOptions, timeframe, today]);
+};
diff --git a/packages/app/src/domain/variants/logic/use-series-config.ts b/packages/app/src/domain/variants/logic/use-series-config.ts
new file mode 100644
index 0000000000..e0bd1c1e39
--- /dev/null
+++ b/packages/app/src/domain/variants/logic/use-series-config.ts
@@ -0,0 +1,60 @@
+import { ColorMatch, VariantChartValue, VariantsStackedAreaTileText } from '~/domain/variants/data-selection/types';
+import { useMemo } from 'react';
+import { GappedAreaSeriesDefinition } from '~/components/time-series-chart/logic';
+
+/**
+ * Create a configuration with appropriate mnemonic label (e.g. "Alpha", "Delta", "Omikron", etc.) and colour for all variants
+ * present in data.
+ * @param text
+ * @param values
+ * @param colors
+ */
+export const useSeriesConfig = (
+ text: VariantsStackedAreaTileText,
+ values: VariantChartValue[],
+ colors: ColorMatch[]
+): readonly [GappedAreaSeriesDefinition[], GappedAreaSeriesDefinition[]] => {
+ return useMemo(() => {
+ const baseVariantsFiltered = values
+ .flatMap((x) => Object.keys(x)) // Get all key names
+ .filter((x, index, array) => array.indexOf(x) === index) // De-dupe keys
+ .filter((x) => x.endsWith('_percentage')) // Filter out any keys that don't end in '_percentage'
+ .reverse(); // Reverse to be in alphabetical order
+
+ /* Enrich config with dynamic data / locale */
+ const seriesConfig: GappedAreaSeriesDefinition[] = [];
+
+ baseVariantsFiltered.forEach((variantKey) => {
+ // Remove _percentage from variant key name
+ const variantCodeFragments = variantKey.split('_');
+ variantCodeFragments.pop();
+ const variantCode = variantCodeFragments.join('_');
+
+ // Match mnenonic variant name in lokalize to code-based variant name
+ const variantDynamicLabel = text.variantCodes[variantCode] + ' '; // THIS IS NECESSARY TO DIFFERENTIATE STATE BETWEEN THE TWO INTERACTIVE LEGENDS ON THE PAGE;
+
+ // Match appropriate variant color
+ const color = colors.find((variantColors) => variantColors.variant === variantCode)?.color;
+
+ // Create a variant label configuration and push into array
+ if (variantDynamicLabel) {
+ const variantConfig = {
+ type: 'gapped-area',
+ metricProperty: variantKey as keyof VariantChartValue,
+ color,
+ label: variantDynamicLabel,
+ strokeWidth: 2,
+ fillOpacity: 0.2,
+ shape: 'gapped-area',
+ mixBlendMode: 'multiply',
+ };
+
+ seriesConfig.push(variantConfig as GappedAreaSeriesDefinition);
+ }
+ });
+
+ const selectOptions: GappedAreaSeriesDefinition[] = [...seriesConfig];
+
+ return [seriesConfig, selectOptions] as const;
+ }, [values, text.tooltip_labels.other_percentage, text.variantCodes, colors]);
+};
diff --git a/packages/app/src/domain/variants/variants-stacked-area-tile/logic/use-unreliable-data-annotations.ts b/packages/app/src/domain/variants/logic/use-unreliable-data-annotations.ts
similarity index 78%
rename from packages/app/src/domain/variants/variants-stacked-area-tile/logic/use-unreliable-data-annotations.ts
rename to packages/app/src/domain/variants/logic/use-unreliable-data-annotations.ts
index b2e36614ad..603e6c4949 100644
--- a/packages/app/src/domain/variants/variants-stacked-area-tile/logic/use-unreliable-data-annotations.ts
+++ b/packages/app/src/domain/variants/logic/use-unreliable-data-annotations.ts
@@ -4,19 +4,14 @@ import { useMemo } from 'react';
import { isDefined } from 'ts-is-present';
import { TimespanAnnotationConfig } from '~/components/time-series-chart/logic';
-export function useUnreliableDataAnnotations(
- values: (DateSpanValue & { is_reliable: boolean })[],
- label: string
-) {
+export function useUnreliableDataAnnotations(values: (DateSpanValue & { is_reliable: boolean })[], label: string) {
return useMemo(
() =>
values
.reduce(
(acc, x) => {
if (!x.is_reliable) {
- const annotation =
- last(acc) ??
- ({ label, fill: 'dotted' } as TimespanAnnotationConfig);
+ const annotation = last(acc) ?? ({ label, fill: 'dotted' } as TimespanAnnotationConfig);
if (!isDefined(annotation.start)) {
annotation.start = x.date_start_unix;
annotation.end = x.date_end_unix;
diff --git a/packages/app/src/domain/variants/static-props/index.ts b/packages/app/src/domain/variants/static-props/index.ts
deleted file mode 100644
index c455be43bc..0000000000
--- a/packages/app/src/domain/variants/static-props/index.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export * from './get-variant-chart-data';
-export * from './get-variant-order-colors';
-export * from './get-variant-table-data';
diff --git a/packages/app/src/domain/variants/variants-stacked-area-tile/variants-stacked-area-tile.tsx b/packages/app/src/domain/variants/variants-stacked-area-tile.tsx
similarity index 71%
rename from packages/app/src/domain/variants/variants-stacked-area-tile/variants-stacked-area-tile.tsx
rename to packages/app/src/domain/variants/variants-stacked-area-tile.tsx
index 753222a673..fbc223b6c2 100644
--- a/packages/app/src/domain/variants/variants-stacked-area-tile/variants-stacked-area-tile.tsx
+++ b/packages/app/src/domain/variants/variants-stacked-area-tile.tsx
@@ -9,17 +9,11 @@ import { MetadataProps } from '~/components/metadata';
import { TimeSeriesChart } from '~/components/time-series-chart';
import { TooltipSeriesList } from '~/components/time-series-chart/components/tooltip/tooltip-series-list';
import { GappedAreaSeriesDefinition } from '~/components/time-series-chart/logic';
-import { VariantChartValue } from '~/domain/variants/static-props';
-import { SiteText } from '~/locale';
import { useList } from '~/utils/use-list';
-import { ColorMatch } from '~/domain/variants/static-props';
-import { useUnreliableDataAnnotations } from './logic/use-unreliable-data-annotations';
import { space } from '~/style/theme';
-import { VariantDynamicLabels } from '../variants-table-tile/types';
-
-type VariantsStackedAreaTileText = {
- variantCodes: VariantDynamicLabels;
-} & SiteText['pages']['variants_page']['nl']['varianten_over_tijd_grafiek'];
+import { useUnreliableDataAnnotations } from './logic/use-unreliable-data-annotations';
+import { ColorMatch, VariantChartValue, VariantsStackedAreaTileText } from '~/domain/variants/data-selection/types';
+import { useSeriesConfig } from '~/domain/variants/logic/use-series-config';
const alwaysEnabled: (keyof VariantChartValue)[] = [];
@@ -122,44 +116,3 @@ const useFilteredSeriesConfig = (seriesConfig: GappedAreaSeriesDefinition compareList.includes(item.metricProperty) || compareList.length === alwaysEnabled.length);
}, [seriesConfig, compareList]);
};
-
-const useSeriesConfig = (text: VariantsStackedAreaTileText, values: VariantChartValue[], variantColors: ColorMatch[]) => {
- return useMemo(() => {
- const baseVariantsFiltered = values
- .flatMap((x) => Object.keys(x))
- .filter((x, index, array) => array.indexOf(x) === index) // de-dupe
- .filter((x) => x.endsWith('_percentage'))
- .reverse(); // Reverse to be in an alphabetical order
-
- /* Enrich config with dynamic data / locale */
- const seriesConfig: GappedAreaSeriesDefinition[] = [];
- baseVariantsFiltered.forEach((variantKey) => {
- const variantCodeFragments = variantKey.split('_');
- variantCodeFragments.pop();
- const variantCode = variantCodeFragments.join('_');
-
- const variantDynamicLabel = text.variantCodes[variantCode];
-
- const color = variantColors.find((variantColors) => variantColors.variant === variantCode)?.color;
-
- if (variantDynamicLabel) {
- const newConfig = {
- type: 'gapped-area',
- metricProperty: variantKey as keyof VariantChartValue,
- color,
- label: variantDynamicLabel,
- strokeWidth: 2,
- fillOpacity: 0.2,
- shape: 'gapped-area',
- mixBlendMode: 'multiply',
- };
-
- seriesConfig.push(newConfig as GappedAreaSeriesDefinition);
- }
- });
-
- const selectOptions = [...seriesConfig];
-
- return [seriesConfig, selectOptions] as const;
- }, [values, text.tooltip_labels.other_percentage, text.variantCodes, variantColors]);
-};
diff --git a/packages/app/src/domain/variants/variants-stacked-area-tile/index.ts b/packages/app/src/domain/variants/variants-stacked-area-tile/index.ts
deleted file mode 100644
index efc8c20500..0000000000
--- a/packages/app/src/domain/variants/variants-stacked-area-tile/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { VariantsStackedAreaTile } from './variants-stacked-area-tile';
diff --git a/packages/app/src/domain/variants/variants-stacked-bar-chart-tile.tsx b/packages/app/src/domain/variants/variants-stacked-bar-chart-tile.tsx
new file mode 100644
index 0000000000..f124b7c2fc
--- /dev/null
+++ b/packages/app/src/domain/variants/variants-stacked-bar-chart-tile.tsx
@@ -0,0 +1,108 @@
+import { ChartTile, MetadataProps } from '~/components';
+import { Spacer } from '~/components/base';
+import { TimeframeOption, TimeframeOptionsList } from '@corona-dashboard/common';
+import { useState } from 'react';
+import { ColorMatch, StackedBarConfig, VariantChartValue, VariantDynamicLabels, VariantsOverTimeGraphText } from '~/domain/variants/data-selection/types';
+import { StackedBarTooltipData, StackedChart } from '~/components/stacked-chart';
+import { useBarConfig } from '~/domain/variants/logic/use-bar-config';
+import { InteractiveLegend } from '~/components/interactive-legend';
+import { useList } from '~/utils/use-list';
+import { TooltipData } from '~/components/time-series-chart/components';
+import { isDefined, isPresent } from 'ts-is-present';
+import { TooltipSeriesList } from '~/components/time-series-chart/components/tooltip/tooltip-series-list';
+import { space } from '~/style/theme';
+import { useCurrentDate } from '~/utils/current-date-context';
+
+interface VariantsStackedBarChartTileProps {
+ title: string;
+ description: string;
+ helpText: string;
+ values: VariantChartValue[];
+ tooltipLabels: VariantsOverTimeGraphText;
+ variantLabels: VariantDynamicLabels;
+ variantColors: ColorMatch[];
+ metadata: MetadataProps;
+}
+
+const alwaysEnabled: (keyof VariantChartValue)[] = [];
+
+/**
+ * Check if the key metricProperty exists
+ * @param config
+ */
+const hasMetricProperty = (config: any): config is { metricProperty: string } => {
+ return 'metricProperty' in config;
+};
+
+/**
+ * Only variants that have a greater occurrence than 0 must be shown in the tooltip, except when the user narrows down
+ * the total amount of visible variants by selecting one or more from the legend
+ * @param context - Tooltip data context
+ * @param selectionOptions - Currently selected variants
+ */
+const reorderAndFilter = (context: TooltipData, selectionOptions: StackedBarConfig[]) => {
+ const metricAmount = context.config.length;
+ const totalMetricAmount = selectionOptions.length;
+ const hasSelectedMetrics = metricAmount !== totalMetricAmount; // Check whether the user has selected any variants from the interactive legend.
+
+ /* Filter out any variants that have an occcurrence value of 0 */
+ const filteredValues = Object.fromEntries(
+ Object.entries(context.value).filter(([key, value]) => (key.includes('occurrence') ? value !== 0 && isPresent(value) && !isNaN(Number(value)) : value))
+ ) as VariantChartValue;
+
+ /* Rebuild tooltip data context with filtered values */
+ const reorderContext = {
+ ...context,
+ config: [...context.config.filter((value) => !hasMetricProperty(value) || filteredValues[value.metricProperty] || hasSelectedMetrics)].filter(isDefined),
+ value: !hasSelectedMetrics ? filteredValues : context.value,
+ };
+
+ return reorderContext as TooltipData;
+};
+
+/**
+ * Variant bar chart component
+ * @param title - Graph title
+ * @param description - Graph description text
+ * @param helpText - Explainer text above the interactive legend
+ * @param values - Data
+ * @param variantLabels - Mnemonic names for variants
+ * @param variantColors - Colors for variants
+ * @param metadata - Metadata block
+ * @constructor
+ */
+export const VariantsStackedBarChartTile = ({ title, description, helpText, tooltipLabels, values, variantLabels, variantColors, metadata }: VariantsStackedBarChartTileProps) => {
+ const today = useCurrentDate();
+
+ const { list, toggle, clear } = useList(alwaysEnabled);
+
+ const [variantTimeFrame, setVariantTimeFrame] = useState(TimeframeOption.THREE_MONTHS);
+
+ const [barChartConfig, selectionOptions] = useBarConfig(values, list, variantLabels, tooltipLabels, variantColors, variantTimeFrame, today);
+
+ const hasTwoColumns = list.length === 0 || list.length > 4;
+
+ return (
+
+
+
+ }
+ />
+
+ );
+};
diff --git a/packages/app/src/domain/variants/variants-table-tile/variants-table-tile.tsx b/packages/app/src/domain/variants/variants-table-tile.tsx
similarity index 60%
rename from packages/app/src/domain/variants/variants-table-tile/variants-table-tile.tsx
rename to packages/app/src/domain/variants/variants-table-tile.tsx
index df622a463c..bd2eb9f760 100644
--- a/packages/app/src/domain/variants/variants-table-tile/variants-table-tile.tsx
+++ b/packages/app/src/domain/variants/variants-table-tile.tsx
@@ -8,24 +8,19 @@ import { FullscreenChartTile } from '~/components/fullscreen-chart-tile';
import { Markdown } from '~/components/markdown';
import { MetadataProps } from '~/components/metadata';
import { Heading } from '~/components/typography';
-import { VariantRow } from '~/domain/variants/static-props';
import { useIntl } from '~/intl';
-import { space } from '~/style/theme';
+import { fontSizes, space } from '~/style/theme';
import { replaceVariablesInText } from '~/utils/replace-variables-in-text';
-import { VariantsTable } from './components/variants-table';
-import { TableText } from './types';
+import { VariantsTable } from './variants-table-tile/components/variants-table';
+import { TableText } from './variants-table-tile/types';
+import { Tile } from '~/components';
+import { VariantRow } from '~/domain/variants/data-selection/types';
-export function VariantsTableTile({
- text,
- noDataMessage = '',
- source,
- data,
- dates,
- children = null,
-}: {
+interface VariantsTableTileProps {
text: TableText;
noDataMessage?: ReactNode;
data?: VariantRow[] | null;
+ sampleThresholdPassed: boolean;
source: {
download: string;
href: string;
@@ -37,7 +32,9 @@ export function VariantsTableTile({
date_of_report_unix: number;
};
children?: ReactNode;
-}) {
+}
+
+export function VariantsTableTile({ text, noDataMessage = '', sampleThresholdPassed, source, data, dates, children = null }: VariantsTableTileProps) {
if (!isPresent(data) || !isPresent(dates)) {
return (
@@ -56,21 +53,16 @@ export function VariantsTableTile({
}
return (
-
+
{children}
);
}
-function VariantsTableTileWithData({
- text,
- source,
- data,
- dates,
- children = null,
-}: {
+interface VariantsTableTileWithDataProps {
text: TableText;
data: VariantRow[];
+ sampleThresholdPassed: boolean;
source: {
download: string;
href: string;
@@ -82,7 +74,9 @@ function VariantsTableTileWithData({
date_of_report_unix: number;
};
children?: ReactNode;
-}) {
+}
+
+function VariantsTableTileWithData({ text, sampleThresholdPassed, source, data, dates, children = null }: VariantsTableTileWithDataProps) {
const { formatDateSpan } = useIntl();
const metadata: MetadataProps = {
@@ -99,12 +93,25 @@ function VariantsTableTileWithData({
});
return (
-
- {children}
-
-
-
-
+ <>
+ {sampleThresholdPassed ? (
+
+ {children}
+
+
+
+
+ ) : (
+
+
+ {text.titel}
+
+
+
+
+
+ )}
+ >
);
}
diff --git a/packages/app/src/domain/variants/variants-table-tile/components/narrow-variants-table.tsx b/packages/app/src/domain/variants/variants-table-tile/components/narrow-variants-table.tsx
index 46591cdbbc..5dd8740668 100644
--- a/packages/app/src/domain/variants/variants-table-tile/components/narrow-variants-table.tsx
+++ b/packages/app/src/domain/variants/variants-table-tile/components/narrow-variants-table.tsx
@@ -4,7 +4,6 @@ import styled from 'styled-components';
import { isPresent } from 'ts-is-present';
import { Box } from '~/components/base';
import { InlineText } from '~/components/typography';
-import { VariantRow } from '~/domain/variants/static-props';
import { useIntl } from '~/intl';
import { space } from '~/style/theme';
import { getMaximumNumberOfDecimals } from '~/utils/get-maximum-number-of-decimals';
@@ -12,6 +11,7 @@ import { useCollapsible } from '~/utils/use-collapsible';
import { Cell, HeaderCell, PercentageBarWithNumber, StyledTable, VariantDifference, VariantNameCell } from '.';
import { TableText } from '../types';
import { NoPercentageData } from './no-percentage-data';
+import { VariantRow } from '~/domain/variants/data-selection/types';
interface NarrowVariantsTableProps {
rows: VariantRow[];
diff --git a/packages/app/src/domain/variants/variants-table-tile/components/variant-difference.tsx b/packages/app/src/domain/variants/variants-table-tile/components/variant-difference.tsx
index 3124720e76..c8e5d50314 100644
--- a/packages/app/src/domain/variants/variants-table-tile/components/variant-difference.tsx
+++ b/packages/app/src/domain/variants/variants-table-tile/components/variant-difference.tsx
@@ -33,7 +33,7 @@ export const VariantDifference = ({ value, text, isWideTable }: VariantDifferenc
{
condition: value?.difference > 0,
renderingValue: (
-
+
{formatPercentage(value.difference, options)} {text.verschil.meer}
@@ -42,7 +42,7 @@ export const VariantDifference = ({ value, text, isWideTable }: VariantDifferenc
{
condition: value?.difference < 0,
renderingValue: (
-
+
{formatPercentage(-value.difference, options)} {text.verschil.minder}
diff --git a/packages/app/src/domain/variants/variants-table-tile/components/variant-name-cell.tsx b/packages/app/src/domain/variants/variants-table-tile/components/variant-name-cell.tsx
index 59567e7993..a58ef0a04b 100644
--- a/packages/app/src/domain/variants/variants-table-tile/components/variant-name-cell.tsx
+++ b/packages/app/src/domain/variants/variants-table-tile/components/variant-name-cell.tsx
@@ -1,7 +1,7 @@
import { BoldText } from '~/components/typography';
import { Cell } from '.';
import { TableText } from '../types';
-import { VariantCode } from '../../static-props';
+import { VariantCode } from '~/domain/variants/data-selection/types';
type VariantNameCellProps = {
variantCode: VariantCode;
diff --git a/packages/app/src/domain/variants/variants-table-tile/components/variants-table.tsx b/packages/app/src/domain/variants/variants-table-tile/components/variants-table.tsx
index 79143c1368..2f1560a73d 100644
--- a/packages/app/src/domain/variants/variants-table-tile/components/variants-table.tsx
+++ b/packages/app/src/domain/variants/variants-table-tile/components/variants-table.tsx
@@ -1,8 +1,8 @@
-import { VariantRow } from '~/domain/variants/static-props';
import { useBreakpoints } from '~/utils/use-breakpoints';
import { TableText } from '../types';
import { NarrowVariantsTable } from './narrow-variants-table';
import { WideVariantsTable } from './wide-variants-table';
+import { VariantRow } from '~/domain/variants/data-selection/types';
type VariantsTableProps = {
rows: VariantRow[];
@@ -12,13 +12,5 @@ type VariantsTableProps = {
export function VariantsTable({ rows, text }: VariantsTableProps) {
const breakpoints = useBreakpoints();
- return (
- <>
- {breakpoints.sm ? (
-
- ) : (
-
- )}
- >
- );
+ return <>{breakpoints.sm ? : }>;
}
diff --git a/packages/app/src/domain/variants/variants-table-tile/components/wide-variants-table.tsx b/packages/app/src/domain/variants/variants-table-tile/components/wide-variants-table.tsx
index 2f53067af4..c9fb40f998 100644
--- a/packages/app/src/domain/variants/variants-table-tile/components/wide-variants-table.tsx
+++ b/packages/app/src/domain/variants/variants-table-tile/components/wide-variants-table.tsx
@@ -2,12 +2,12 @@ import { DifferenceDecimal } from '@corona-dashboard/common';
import { useMemo } from 'react';
import { isPresent } from 'ts-is-present';
import { Box } from '~/components/base';
-import { VariantRow } from '~/domain/variants/static-props';
import { useIntl } from '~/intl';
import { getMaximumNumberOfDecimals } from '~/utils/get-maximum-number-of-decimals';
import { Cell, HeaderCell, PercentageBarWithNumber, StyledTable, VariantDifference, VariantNameCell } from '.';
import { TableText } from '../types';
import { NoPercentageData } from './no-percentage-data';
+import { VariantRow } from '~/domain/variants/data-selection/types';
const columnKeys = ['variant_titel', 'percentage', 'vorige_meting'] as const;
diff --git a/packages/app/src/domain/variants/variants-table-tile/index.ts b/packages/app/src/domain/variants/variants-table-tile/index.ts
deleted file mode 100644
index 292e200747..0000000000
--- a/packages/app/src/domain/variants/variants-table-tile/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './variants-table-tile';
diff --git a/packages/app/src/domain/variants/variants-table-tile/types.ts b/packages/app/src/domain/variants/variants-table-tile/types.ts
index 3f19cd5577..9132720e62 100644
--- a/packages/app/src/domain/variants/variants-table-tile/types.ts
+++ b/packages/app/src/domain/variants/variants-table-tile/types.ts
@@ -1,4 +1,4 @@
-export type VariantDynamicLabels = Record;
+import { VariantDynamicLabels } from '~/domain/variants/data-selection/types';
export type TableText = {
anderen_tooltip: string;
diff --git a/packages/app/src/pages/landelijk/vaccinaties.tsx b/packages/app/src/pages/landelijk/vaccinaties.tsx
index 9716386f8f..aebe45e4e1 100644
--- a/packages/app/src/pages/landelijk/vaccinaties.tsx
+++ b/packages/app/src/pages/landelijk/vaccinaties.tsx
@@ -352,7 +352,6 @@ function VaccinationPage(props: StaticProps) {
) {
datumsText: textNl.dates,
date: archivedData.vaccine_campaigns_archived_20231004.date_unix,
source: textNl.vaccine_campaigns.bronnen.rivm,
+ disclaimer: textNl.vaccine_campaigns.description_footer,
}}
/>
@@ -427,7 +427,6 @@ function VaccinationPage(props: StaticProps) {
) {
datumsText: textNl.dates,
date: archivedData.vaccine_campaigns_archived_20220908.date_unix,
source: textNl.vaccine_campaigns.bronnen.rivm,
+ disclaimer: textNl.vaccine_campaigns.description_footer,
}}
/>
diff --git a/packages/app/src/pages/landelijk/varianten.tsx b/packages/app/src/pages/landelijk/varianten.tsx
index 888702005c..0e569c9728 100644
--- a/packages/app/src/pages/landelijk/varianten.tsx
+++ b/packages/app/src/pages/landelijk/varianten.tsx
@@ -7,19 +7,20 @@ import { PageInformationBlock } from '~/components/page-information-block';
import { TileList } from '~/components/tile-list';
import { Layout } from '~/domain/layout/layout';
import { NlLayout } from '~/domain/layout/nl-layout';
-import { getVariantChartData, getVariantOrderColors, getVariantTableData } from '~/domain/variants/static-props';
-import { VariantsStackedAreaTile } from '~/domain/variants/variants-stacked-area-tile';
-import { VariantsTableTile } from '~/domain/variants/variants-table-tile';
-import { VariantDynamicLabels } from '~/domain/variants/variants-table-tile/types';
import { useIntl } from '~/intl';
import { Languages, SiteText } from '~/locale';
import { getArticleParts, getDataExplainedParts, getFaqParts, getLinkParts, getPagePartsQuery } from '~/queries/get-page-parts-query';
import { StaticProps, createGetStaticProps } from '~/static-props/create-get-static-props';
-import { createGetContent, getLastGeneratedDate, getLokalizeTexts, selectNlData } from '~/static-props/get-data';
+import { createGetContent, getLastGeneratedDate, getLokalizeTexts, selectArchivedNlData, selectNlData } from '~/static-props/get-data';
import { ArticleParts, LinkParts, PagePartQueryResult } from '~/types/cms';
import { useDynamicLokalizeTexts } from '~/utils/cms/use-dynamic-lokalize-texts';
import { getLastInsertionDateOfPage } from '~/utils/get-last-insertion-date-of-page';
import { getPageInformationHeaderContent } from '~/utils/get-page-information-header-content';
+import { BorderedKpiSection } from '~/components/kpi/bordered-kpi-section';
+import { useState } from 'react';
+import { getArchivedVariantChartData, getVariantBarChartData, getVariantOrderColors, getVariantTableData } from '~/domain/variants/data-selection';
+import { VariantsStackedAreaTile, VariantsStackedBarChartTile, VariantsTableTile } from '~/domain/variants';
+import { VariantDynamicLabels } from '~/domain/variants/data-selection/types';
const pageMetrics = ['variants', 'named_difference'];
@@ -36,16 +37,22 @@ export const getStaticProps = createGetStaticProps(
getLastGeneratedDate,
() => {
const data = selectNlData('variants', 'named_difference')();
+ const archivedData = selectArchivedNlData('variants_archived_20231101')();
const {
selectedNlData: { variants },
} = data;
+ const {
+ selectedArchivedNlData: { variants_archived_20231101 },
+ } = archivedData;
+
const variantColors = getVariantOrderColors(variants);
return {
...getVariantTableData(variants, data.selectedNlData.named_difference, variantColors),
- ...getVariantChartData(variants),
+ ...getVariantBarChartData(variants),
+ ...getArchivedVariantChartData(variants_archived_20231101),
variantColors,
};
},
@@ -64,11 +71,13 @@ export const getStaticProps = createGetStaticProps(
);
export default function CovidVariantenPage(props: StaticProps) {
- const { pageText, selectedNlData: data, lastGenerated, content, variantTable, variantChart, variantColors, dates } = props;
+ const { pageText, selectedNlData: data, lastGenerated, content, variantTable, variantChart, archivedVariantChart, variantColors, dates } = props;
const { commonTexts, locale } = useIntl();
const { metadataTexts, textNl } = useDynamicLokalizeTexts(pageText, selectLokalizeTexts);
+ const [isArchivedContentShown, setIsArchivedContentShown] = useState(false);
+
const metadata = {
...metadataTexts,
title: textNl.metadata.title,
@@ -77,8 +86,17 @@ export default function CovidVariantenPage(props: StaticProps namedDifferenceEntry.variant_code !== 'other_variants').length;
+ const totalVariants = data.variants
+ ? data.variants!.values.reduce((accumulator, currentVariant) => (currentVariant.last_value.occurrence > 0 ? 1 + accumulator : accumulator), 0)
+ : NaN;
+
+ const sampleThresholdPassed = data.variants ? data.variants!.values[0].last_value.sample_size > 100 : false;
+
const variantLabels: VariantDynamicLabels = {};
+ const variantenTableDescription = sampleThresholdPassed ? textNl.varianten_omschrijving : textNl.varianten_tabel.omschrijving_te_weinig_samples;
+
data.variants?.values.forEach((variant) => {
variantLabels[`${variant.variant_code}`] = locale === 'nl' ? variant.values[0].label_nl : variant.values[0].label_en;
});
@@ -109,13 +127,37 @@ export default function CovidVariantenPage(props: StaticProps
+
+
{variantChart && variantLabels && (
-
)}
+
+ setIsArchivedContentShown(!isArchivedContentShown)}
+ />
+
+ {isArchivedContentShown && (
+ <>
+ {archivedVariantChart && variantLabels && (
+
+ )}
+ >
+ )}
diff --git a/packages/cms/src/lokalize/key-mutations.csv b/packages/cms/src/lokalize/key-mutations.csv
index 4f77c57842..d6e70b0f18 100644
--- a/packages/cms/src/lokalize/key-mutations.csv
+++ b/packages/cms/src/lokalize/key-mutations.csv
@@ -1,3 +1,15 @@
timestamp,action,key,document_id,move_to
2023-10-16T15:02:56.856Z,add,pages.vaccinations_page.nl.vaccine_campaigns.campaigns.autumn_round_corona_vaccination_2022_description,tWOr2ZtVUADiKBNT0r4Txl,__
2023-10-16T15:02:56.857Z,delete,pages.vaccinations_page.nl.vaccine_campaigns.campaigns.repeat_vaccination_against_corona_description,DNO5adeD4mWNc516AbctlW,__
+2023-10-20T09:31:18.217Z,add,pages.variants_page.nl.section_archived.title,093FZrW2Ae4fRiIdZYDcnH,__
+2023-10-20T09:31:19.500Z,add,pages.variants_page.nl.section_archived.description,hT5k3RDQ7JafeiQP6wRLNE,__
+2023-10-20T09:31:20.726Z,add,pages.variants_page.nl.varianten_barchart.titel,093FZrW2Ae4fRiIdZYDd0v,__
+2023-10-20T09:31:21.806Z,add,pages.variants_page.nl.varianten_barchart.description,ZkwHqMQjnsmR1ekP50NJ35,__
+2023-10-20T09:31:22.794Z,add,pages.variants_page.nl.kpi_amount_of_samples.kpi_tile_title,ZkwHqMQjnsmR1ekP50NJ65,__
+2023-10-20T09:31:23.837Z,add,pages.variants_page.nl.kpi_amount_of_samples.kpi_tile_description,093FZrW2Ae4fRiIdZYDdEZ,__
+2023-10-20T09:31:24.874Z,add,pages.variants_page.nl.kpi_amount_of_samples.tile_total_samples.title,ZkwHqMQjnsmR1ekP50NJ95,__
+2023-10-20T09:31:25.881Z,add,pages.variants_page.nl.kpi_amount_of_samples.tile_total_samples.description,hT5k3RDQ7JafeiQP6wRLY1,__
+2023-10-20T09:31:26.938Z,add,pages.variants_page.nl.kpi_amount_of_samples.tile_total_variants.title,pmfpKotscgjR5sJqbeu52i,__
+2023-10-20T09:31:28.284Z,add,pages.variants_page.nl.kpi_amount_of_samples.tile_total_variants.description,hT5k3RDQ7JafeiQP6wRLio,__
+2023-10-20T09:31:29.643Z,add,pages.variants_page.nl.kpi_amount_of_samples.disclaimer,w5vHLm19hF0S5wj1e5Ryjx,__
+2023-10-20T09:31:30.622Z,add,pages.variants_page.nl.varianten_tabel.omschrijving_te_weinig_samples,w5vHLm19hF0S5wj1e5RyoG,__
diff --git a/packages/cms/src/studio/data/data-structure.ts b/packages/cms/src/studio/data/data-structure.ts
index 3c2c8b2b04..9a2ffa395b 100644
--- a/packages/cms/src/studio/data/data-structure.ts
+++ b/packages/cms/src/studio/data/data-structure.ts
@@ -261,6 +261,7 @@ export const dataStructure = {
'janssen_not_available',
'janssen_total',
],
+ variants_archived_20231101: ['variant_code', 'values', 'last_value'],
repeating_shot_administered_20220713: ['ggd_administered_total'],
corona_melder_app_warning_archived_20220421: ['count'],
corona_melder_app_download_archived_20220421: ['count'],
diff --git a/packages/common/src/data-sorting.ts b/packages/common/src/data-sorting.ts
index 6e167d259a..7198de5eb5 100644
--- a/packages/common/src/data-sorting.ts
+++ b/packages/common/src/data-sorting.ts
@@ -1,5 +1,5 @@
import { isDefined } from 'ts-is-present';
-import { GmSewerPerInstallationValue, NlVariantsVariantValue } from './types';
+import { ArchivedNlVariantsVariantValue, GmSewerPerInstallationValue, NlVariantsVariantValue } from './types';
export type UnknownObject = Record;
@@ -84,40 +84,49 @@ export function sortTimeSeriesInDataInPlace(data: T, { setDatesToMiddleOfDay
* The variants data is structured similarly to sewer_per_installation as
* shown above. @TODO unify/clean up validation of both.
*/
- if (isDefined((data as UnknownObject).variants)) {
- const nestedSeries = (data as UnknownObject).variants as VariantsData;
+ if (isDefined((data as UnknownObject).variants) || isDefined((data as UnknownObject).variants_archived_20231101)) {
+ let nestedSeries;
- if (!nestedSeries.values) {
- /**
- * It can happen that we get incomplete json data and assuming that values
- * exists here might crash the app
- */
- console.error('variants.values does not exist');
- return;
+ if (isDefined((data as UnknownObject).variants)) {
+ nestedSeries = (data as UnknownObject).variants as VariantsData;
+ }
+ if (isDefined((data as UnknownObject).variants_archived_20231101)) {
+ nestedSeries = (data as UnknownObject).variants_archived_20231101 as VariantsData;
}
- nestedSeries.values = nestedSeries.values.map((x, index) => {
- if (!x.values) {
+ if (nestedSeries) {
+ if (!nestedSeries.values) {
/**
* It can happen that we get incomplete json data and assuming that values
* exists here might crash the app
*/
- console.error(`variants.nestedSeries.values[${index}].values does not exist`);
- return x;
+ console.error('variants.values does not exist');
+ return;
}
- x.values = sortTimeSeriesValues(x.values) as NlVariantsVariantValue[];
+ nestedSeries.values = nestedSeries.values.map((x, index) => {
+ if (!x.values) {
+ /**
+ * It can happen that we get incomplete json data and assuming that values
+ * exists here might crash the app
+ */
+ console.error(`variants.nestedSeries.values[${index}].values does not exist`);
+ return x;
+ }
- if (setDatesToMiddleOfDay) {
- x.values = x.values.map(setValueDatesToMiddleOfDay);
+ x.values = sortTimeSeriesValues(x.values) as ArchivedNlVariantsVariantValue[];
- if (x.last_value) {
- x.last_value = setValueDatesToMiddleOfDay(x.last_value);
+ if (setDatesToMiddleOfDay) {
+ x.values = x.values.map(setValueDatesToMiddleOfDay);
+
+ if (x.last_value) {
+ x.last_value = setValueDatesToMiddleOfDay(x.last_value);
+ }
}
- }
- return x;
- });
+ return x;
+ });
+ }
}
}
diff --git a/packages/common/src/types/data.ts b/packages/common/src/types/data.ts
index bda448e74c..1a02f50e78 100644
--- a/packages/common/src/types/data.ts
+++ b/packages/common/src/types/data.ts
@@ -231,6 +231,7 @@ export interface ArchivedNl {
vaccine_coverage_per_age_group_estimated_fully_vaccinated_archived_20231004: NlVaccineCoveragePerAgeGroupEstimatedFullyVaccinatedValue;
vaccine_delivery_per_supplier_archived_20211101: ArchivedNlVaccineDeliveryPerSupplier;
vaccine_stock_archived_20211024: ArchivedNlVaccineStock;
+ variants_archived_20231101: ArchivedNlVariants;
repeating_shot_administered_20220713: ArchivedNlRepeatingShotAdministered;
corona_melder_app_warning_archived_20220421: ArchivedNlCoronaMelderAppWarning;
corona_melder_app_download_archived_20220421: ArchivedNlCoronaMelderAppDownload;
@@ -833,6 +834,26 @@ export interface ArchivedNlVaccineStockValue {
date_of_insertion_unix: number;
date_unix: number;
}
+export interface ArchivedNlVariants {
+ values: ArchivedNlVariantsVariant[];
+}
+export interface ArchivedNlVariantsVariant {
+ variant_code: string;
+ values: ArchivedNlVariantsVariantValue[];
+ last_value: ArchivedNlVariantsVariantValue;
+}
+export interface ArchivedNlVariantsVariantValue {
+ order: number;
+ occurrence: number;
+ percentage: number;
+ sample_size: number;
+ date_start_unix: number;
+ date_end_unix: number;
+ date_of_insertion_unix: number;
+ date_of_report_unix: number;
+ label_nl: string;
+ label_en: string;
+}
export interface ArchivedNlRepeatingShotAdministered {
values: ArchivedNlRepeatingShotAdministeredValue[];
last_value: ArchivedNlRepeatingShotAdministeredValue;
@@ -1031,7 +1052,7 @@ export interface Nl {
deceased_cbs: NlDeceasedCbs;
vaccine_administered_last_timeframe: NlVaccineAdministeredLastTimeframe;
vaccine_campaigns: NlVaccineCampaign;
- variants?: NlVariants;
+ variants: NlVariants;
self_test_overall: NlSelfTestOverall;
infectionradar_symptoms_trend_per_age_group_weekly: NlInfectionradarSymptomsTrendPerAgeGroupWeekly;
}