Skip to content
This repository has been archived by the owner on Nov 4, 2024. It is now read-only.

Feature/COR-1262-infection-radar-main #4556

Merged
merged 5 commits into from
Dec 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions docs/timeline-events.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
# Timeline Events
# Timelines && Timeline Events

All time-series charts can optionally render events on a timeline to illustrate
and describe certain parts of the data.

These events are managed via the CMS.

To enable and configure events for a chart, two things need to
happen. First we need to add the chart instance as an "element" to the CMS
happen. First we need to add the chart instance as an `element` to the CMS
dataset, and secondly we need to query the element data in the page where the
chart is used, and pass the results to the component.

## CMS Data Elements

The "elements" section in the CMS provides a flexible way to connect any sort of
The `elements` section in the CMS provides a flexible way to connect any sort of
configuration to a data element on one of the pages. Unlike the various pages
defined in the CMS, the elements works as a flat collection of objects that each
point to a unique data element in the app via a combination of `scope`, `element type`, `metric name` and optionally a `metric property`.
Expand All @@ -27,13 +27,16 @@ we have an element with id `nl__tested_overall__time_series`.

## Adding An Element

Adding a new element is only possible via a command-line interface.
Adding a new element is **only** possible via a command-line interface.

This script can be run using `packages/cms/yarn elements:add`.

The script is self-explanatory as it allows the user to select the data item
using a series of selection lists that are generated from the schema's.

NOTE: When you use this script, the element will be created in both development
and production datasets.

## Query Elements Data

Data for all elements on the pages is fetched together as part of the getStaticProps
Expand Down
4 changes: 4 additions & 0 deletions packages/app/schema/nl/__index.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"nursing_home",
"proto_name",
"reproduction",
"self_test_overall",
"sewer",
"tested_ggd",
"tested_ggd_archived",
Expand Down Expand Up @@ -241,6 +242,9 @@
},
"variants": {
"$ref": "variants.json"
},
"self_test_overall": {
"$ref": "self_test_overall.json"
}
},
"$defs": {
Expand Down
40 changes: 40 additions & 0 deletions packages/app/schema/nl/self_test_overall.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"definitions": {
"value": {
"title": "nl_self_test_overall_value",
"type": "object",
"required": ["infected_percentage", "date_start_unix", "date_end_unix", "date_of_insertion_unix"],
"additionalProperties": false,
"properties": {
"infected_percentage": {
"type": ["number", "null"]
},
"date_start_unix": {
"type": "integer"
},
"date_end_unix": {
"type": "integer"
},
"date_of_insertion_unix": {
"type": "integer"
}
}
}
},
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "nl_self_test_overall",
"type": "object",
"required": ["values", "last_value"],
"additionalProperties": false,
"properties": {
"values": {
"type": "array",
"items": {
"$ref": "#/definitions/value"
}
},
"last_value": {
"$ref": "#/definitions/value"
}
}
}
46 changes: 10 additions & 36 deletions packages/app/src/components/time-series-chart/logic/legend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,7 @@ import { useMemo } from 'react';
import { isDefined } from 'ts-is-present';
import { LegendItem } from '~/components/legend';
import { useIntl } from '~/intl';
import {
HatchedTimespanAnnotationIcon,
OutOfBoundsIcon,
SeriesIcon,
SolidTimespanAnnotationIcon,
} from '../components';
import { HatchedTimespanAnnotationIcon, OutOfBoundsIcon, SeriesIcon, SolidTimespanAnnotationIcon } from '../components';
import { TimelineMarker } from '../components/timeline';
import { isVisibleEvent } from '../components/timeline/logic';
import { DataOptions } from './common';
Expand All @@ -21,11 +16,10 @@ export function useLegendItems<T extends TimestampedValue>(
domain: number[],
config: SeriesConfig<T>,
dataOptions?: DataOptions,
hasOutofBoudsValues = false,
hasOutOfBoundsValues = false,
forceLegend = false
) {
const { timelineEvents, timespanAnnotations, outOfBoundsConfig } =
dataOptions || {};
const { timelineEvents, timespanAnnotations, outOfBoundsConfig } = dataOptions || {};
const { commonTexts } = useIntl();

return useMemo(() => {
Expand Down Expand Up @@ -53,7 +47,7 @@ export function useLegendItems<T extends TimestampedValue>(
})
.filter(isDefined);

if (hasOutofBoudsValues) {
if (hasOutOfBoundsValues) {
legendItems.push({
label: outOfBoundsConfig?.label,
shape: 'custom',
Expand All @@ -66,20 +60,13 @@ export function useLegendItems<T extends TimestampedValue>(
*/
if (timespanAnnotations) {
for (const annotation of timespanAnnotations) {
const isAnnotationVisible =
(first(domain) as number) <= annotation.end &&
annotation.start <= (last(domain) as number);
const isAnnotationVisible = (first(domain) as number) <= annotation.end && annotation.start <= (last(domain) as number);

if (isAnnotationVisible) {
legendItems.push({
label: annotation.label,
shape: 'custom',
shapeComponent:
annotation.fill === 'solid' || !isDefined(annotation.fill) ? (
<SolidTimespanAnnotationIcon />
) : (
<HatchedTimespanAnnotationIcon />
),
shapeComponent: annotation.fill === 'solid' || !isDefined(annotation.fill) ? <SolidTimespanAnnotationIcon /> : <HatchedTimespanAnnotationIcon />,
} as LegendItem);
}
}
Expand All @@ -89,9 +76,7 @@ export function useLegendItems<T extends TimestampedValue>(
* Add timeline events to the legend
*/
if (timelineEvents) {
const hasVisibleEvents = timelineEvents.some((x) =>
isVisibleEvent(x, domain)
);
const hasVisibleEvents = timelineEvents.some((x) => isVisibleEvent(x, domain));

if (hasVisibleEvents) {
legendItems.push({
Expand Down Expand Up @@ -125,22 +110,11 @@ export function useLegendItems<T extends TimestampedValue>(
* one) to determine if a legend is required. We only have to render a
* legend when there's at least two items.
*/
const isLegendRequired =
forceLegend || legendItems.length + splitLegendGroups.length > 1;
const isLegendRequired = forceLegend || legendItems.length + splitLegendGroups.length > 1;

return {
legendItems: isLegendRequired ? legendItems : undefined,
splitLegendGroups:
splitLegendGroups.length > 0 ? splitLegendGroups : undefined,
splitLegendGroups: splitLegendGroups.length > 0 ? splitLegendGroups : undefined,
};
}, [
config,
domain,
commonTexts,
timelineEvents,
timespanAnnotations,
outOfBoundsConfig,
hasOutofBoudsValues,
forceLegend,
]);
}, [config, domain, commonTexts, timelineEvents, timespanAnnotations, outOfBoundsConfig, hasOutOfBoundsValues, forceLegend]);
}
44 changes: 41 additions & 3 deletions packages/app/src/pages/landelijk/positief-geteste-mensen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const getStaticProps = createGetStaticProps(
'difference.tested_overall__infected_moving_average',
'difference.tested_overall__infected_per_100k_moving_average',
'g_number',
'self_test_overall',
'tested_ggd',
'tested_overall',
'tested_per_age_group'
Expand All @@ -56,8 +57,8 @@ export const getStaticProps = createGetStaticProps(
}>((context) => {
const { locale } = context;
return `{
"parts": ${getPagePartsQuery('positive_tests_page')},
"elements": ${getElementsQuery('nl', ['tested_overall', 'tested_ggd', 'tested_per_age_group'], locale)}
"parts": ${getPagePartsQuery('positive_tests_page')},
"elements": ${getElementsQuery('nl', ['tested_overall', 'tested_ggd', 'tested_per_age_group', 'self_test_overall'], locale)}
}`;
})(context);
return {
Expand Down Expand Up @@ -94,7 +95,9 @@ const GgdGraphToggle = ({ selectedGgdGraph, onChange }: { selectedGgdGraph: stri
function PositivelyTestedPeople(props: StaticProps<typeof getStaticProps>) {
const { pageText, selectedNlData: data, choropleth, content, lastGenerated } = props;

const [confirmedCasesInfectedTimeframe, setConfirmedCasesInfectedTimeframe] = useState<TimeframeOption>(TimeframeOption.ALL);
const [confirmedCasesSelfTestedTimeframe, setConfirmedCasesSelfTestedTimeframe] = useState<TimeframeOption>(TimeframeOption.SIX_MONTHS);

const [confirmedCasesInfectedTimeframe, setConfirmedCasesInfectedTimeframe] = useState<TimeframeOption>(TimeframeOption.SIX_MONTHS);

const [confirmedCasesInfectedPercentageTimeframe, setConfirmedCasesInfectedPercentageTimeframe] = useState<TimeframeOption>(TimeframeOption.ALL);

Expand Down Expand Up @@ -142,6 +145,40 @@ function PositivelyTestedPeople(props: StaticProps<typeof getStaticProps>) {
articles={content.articles}
/>

{/* Infection radar */}
<ChartTile
title={textNl.linechart_self_test_titel}
description={textNl.linechart_self_test_toelichting}
metadata={{
source: textNl.bronnen.rivm,
}}
timeframeOptions={TimeframeOptionsList}
timeframeInitialValue={TimeframeOption.SIX_MONTHS}
onSelectTimeframe={setConfirmedCasesSelfTestedTimeframe}
>
<TimeSeriesChart
accessibility={{
key: 'confirmed_cases_self_tested_over_time_chart',
}}
values={data.self_test_overall.values}
timeframe={confirmedCasesSelfTestedTimeframe}
seriesConfig={[
{
type: 'line',
metricProperty: 'infected_percentage',
label: textNl.linechart_self_test_tooltip_label,
color: colors.primary,
},
]}
dataOptions={{
isPercentage: true,
timelineEvents: getTimelineEvents(content.elements.timeSeries, 'self_test_overall'),
}}
forceLegend
/>
</ChartTile>
{/* Infection radar end */}

<ChartTile
title={textNl.linechart_titel}
description={replaceVariablesInText(textNl.linechart_toelichting, {
Expand All @@ -153,6 +190,7 @@ function PositivelyTestedPeople(props: StaticProps<typeof getStaticProps>) {
source: textNl.bronnen.rivm,
}}
timeframeOptions={TimeframeOptionsList}
timeframeInitialValue={TimeframeOption.SIX_MONTHS}
onSelectTimeframe={setConfirmedCasesInfectedTimeframe}
>
<TimeSeriesChart
Expand Down
5 changes: 1 addition & 4 deletions packages/cms/src/data/data-structure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ export const dataStructure = {
'janssen_total',
],
variants: ['variant_code', 'values', 'last_value'],
self_test_overall: ['infected_percentage'],
},
topical: {},
vr: {
Expand All @@ -304,10 +305,6 @@ export const dataStructure = {
'admissions_on_date_of_admission_moving_average_rounded',
'admissions_on_date_of_reporting',
],
hospital_nice_choropleth: [
"admissions_on_date_of_admission",
"admissions_on_date_of_reporting",
],
tested_ggd: [
'infected',
'infected_moving_average',
Expand Down
1 change: 1 addition & 0 deletions packages/cms/src/elements/schemas/shared/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ const titleByMetricName: Partial<Record<MetricName, string>> = {
hospital_nice_per_age_group: 'Ziekenhuisopnames (per leeftijd)',
tested_per_age_group: 'Positief getest (per leeftijd)',
elderly_at_home: '70-plussers',
self_test_overall: 'Zelfgerapporteerde positieve coronatestuitslagen',
};

function getTitleForMetricName(metricName: MetricName) {
Expand Down
5 changes: 5 additions & 0 deletions packages/cms/src/lokalize/key-mutations.csv
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,8 @@ timestamp,action,key,document_id,move_to
2022-12-19T14:40:04.536Z,delete,pages.disability_care_page.nl.besmette_locaties.linechart_metric_label,jF33EuwumlGuwav2FD3vZU,__
2022-12-19T14:40:04.543Z,delete,pages.disability_care_page.nl.besmette_locaties.linechart_titel,jF33EuwumlGuwav2FD3vAK,__
2022-12-19T14:40:04.552Z,delete,pages.reproduction_page.nl.lineLegendLabel,jF33EuwumlGuwav2FD3aUm,__
2022-12-21T14:44:25.746Z,add,common.accessibility.charts.confirmed_cases_self_tested_over_time_chart.description,KoLyCpXIGU95m7jfEHTDR2,__
2022-12-21T14:44:26.728Z,add,common.accessibility.charts.confirmed_cases_self_tested_over_time_chart.label,PKXdxxxKAnTg0F1ZCgYHiV,__
2022-12-21T16:15:20.048Z,add,pages.positive_tests_page.nl.linechart_self_test_titel,PKXdxxxKAnTg0F1ZCghMkN,__
2022-12-21T16:15:20.949Z,add,pages.positive_tests_page.nl.linechart_self_test_toelichting,PKXdxxxKAnTg0F1ZCghMnz,__
2022-12-21T16:15:22.001Z,add,pages.positive_tests_page.nl.linechart_self_test_tooltip_label,KoLyCpXIGU95m7jfEHZKIf,__
12 changes: 12 additions & 0 deletions packages/common/src/types/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ export interface Nl {
vaccine_coverage_per_age_group_estimated_archived_20220908: NlVaccineCoveragePerAgeGroupEstimatedArchived_20220908Value;
vaccine_stock: NlVaccineStock;
variants?: NlVariants;
self_test_overall: NlSelfTestOverall;
}
export interface NlDifference {
tested_overall__infected_per_100k_moving_average: DifferenceDecimal;
Expand Down Expand Up @@ -1134,12 +1135,23 @@ export interface NlVariantsVariantValue {
date_of_insertion_unix: number;
date_of_report_unix: number;
}
export interface NlSelfTestOverall {
values: NlSelfTestOverallValue[];
last_value: NlSelfTestOverallValue;
}
export interface NlSelfTestOverallValue {
infected_percentage: number | null;
date_start_unix: number;
date_end_unix: number;
date_of_insertion_unix: number;
}

export type TopicalIcon =
| 'AfstandSporten'
| 'AlcoholVerkoop'
| 'Archive'
| 'Arrow'
| 'ArrowWithIntensity'
APW26 marked this conversation as resolved.
Show resolved Hide resolved
| 'Arts'
| 'Avondklok'
| 'BarChart'
Expand Down