From 26aa903538f56daa6fad882c58d405968cf69f78 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Oct 2023 09:20:49 +0200 Subject: [PATCH 01/19] chore(deps-dev): bump postcss from 8.4.4 to 8.4.31 (#4890) Bumps [postcss](https://github.com/postcss/postcss) from 8.4.4 to 8.4.31. - [Release notes](https://github.com/postcss/postcss/releases) - [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/postcss/postcss/compare/8.4.4...8.4.31) --- updated-dependencies: - dependency-name: postcss dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/app/package.json | 2 +- yarn.lock | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/app/package.json b/packages/app/package.json index db716dbbe3..95805e8acf 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -122,7 +122,7 @@ "next-transpile-modules": "^9.0.0", "node-mocks-http": "^1.11.0", "npm-run-all": "^4.1.5", - "postcss": "^8.4.4", + "postcss": "^8.4.31", "postcss-flexbugs-fixes": "^5.0.2", "postcss-preset-env": "^6.7.0", "react-test-renderer": "^17.0.2", diff --git a/yarn.lock b/yarn.lock index e7f23b7991..8f7d5e68e2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3954,7 +3954,7 @@ __metadata: node-mocks-http: ^1.11.0 npm-run-all: ^4.1.5 polished: ^4.1.3 - postcss: ^8.4.4 + postcss: ^8.4.31 postcss-flexbugs-fixes: ^5.0.2 postcss-preset-env: ^6.7.0 react: ^17.0.2 @@ -26339,14 +26339,14 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.4.4": - version: 8.4.4 - resolution: "postcss@npm:8.4.4" +"postcss@npm:^8.4.31": + version: 8.4.31 + resolution: "postcss@npm:8.4.31" dependencies: - nanoid: ^3.1.30 + nanoid: ^3.3.6 picocolors: ^1.0.0 - source-map-js: ^1.0.1 - checksum: 6cf3fe0ecdf5a0d2aeb5e8404938c7eab968704e2e29dc5421e90b4014eb1975c1c0ad828425f2428807ef6e3fcfadd71f988ab55cb06c28ac2866f22403255b + source-map-js: ^1.0.2 + checksum: 1d8611341b073143ad90486fcdfeab49edd243377b1f51834dc4f6d028e82ce5190e4f11bb2633276864503654fb7cab28e67abdc0fbf9d1f88cad4a0ff0beea languageName: node linkType: hard From 857804b04f756e463610cad9eef931ff416308b6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Oct 2023 09:57:30 +0200 Subject: [PATCH 02/19] chore(deps): bump @babel/traverse from 7.15.4 to 7.23.2 (#4906) Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.15.4 to 7.23.2. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse) --- updated-dependencies: - dependency-name: "@babel/traverse" dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 169 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 124 insertions(+), 45 deletions(-) diff --git a/yarn.lock b/yarn.lock index 8f7d5e68e2..60e40a87ff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -90,6 +90,16 @@ __metadata: languageName: node linkType: hard +"@babel/code-frame@npm:^7.22.13": + version: 7.22.13 + resolution: "@babel/code-frame@npm:7.22.13" + dependencies: + "@babel/highlight": ^7.22.13 + chalk: ^2.4.2 + checksum: 22e342c8077c8b77eeb11f554ecca2ba14153f707b85294fcf6070b6f6150aae88a7b7436dd88d8c9289970585f3fe5b9b941c5aa3aa26a6d5a8ef3f292da058 + languageName: node + linkType: hard + "@babel/compat-data@npm:^7.13.11, @babel/compat-data@npm:^7.15.0": version: 7.15.0 resolution: "@babel/compat-data@npm:7.15.0" @@ -215,7 +225,7 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.15.4, @babel/generator@npm:^7.15.8": +"@babel/generator@npm:^7.15.8": version: 7.15.8 resolution: "@babel/generator@npm:7.15.8" dependencies: @@ -249,6 +259,18 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.23.0": + version: 7.23.0 + resolution: "@babel/generator@npm:7.23.0" + dependencies: + "@babel/types": ^7.23.0 + "@jridgewell/gen-mapping": ^0.3.2 + "@jridgewell/trace-mapping": ^0.3.17 + jsesc: ^2.5.1 + checksum: 8efe24adad34300f1f8ea2add420b28171a646edc70f2a1b3e1683842f23b8b7ffa7e35ef0119294e1901f45bfea5b3dc70abe1f10a1917ccdfb41bed69be5f1 + languageName: node + linkType: hard + "@babel/helper-annotate-as-pure@npm:^7.14.5, @babel/helper-annotate-as-pure@npm:^7.15.4": version: 7.15.4 resolution: "@babel/helper-annotate-as-pure@npm:7.15.4" @@ -495,6 +517,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-environment-visitor@npm:^7.22.20": + version: 7.22.20 + resolution: "@babel/helper-environment-visitor@npm:7.22.20" + checksum: d80ee98ff66f41e233f36ca1921774c37e88a803b2f7dca3db7c057a5fea0473804db9fb6729e5dbfd07f4bed722d60f7852035c2c739382e84c335661590b69 + languageName: node + linkType: hard + "@babel/helper-explode-assignable-expression@npm:^7.15.4": version: 7.15.4 resolution: "@babel/helper-explode-assignable-expression@npm:7.15.4" @@ -545,6 +574,16 @@ __metadata: languageName: node linkType: hard +"@babel/helper-function-name@npm:^7.23.0": + version: 7.23.0 + resolution: "@babel/helper-function-name@npm:7.23.0" + dependencies: + "@babel/template": ^7.22.15 + "@babel/types": ^7.23.0 + checksum: e44542257b2d4634a1f979244eb2a4ad8e6d75eb6761b4cfceb56b562f7db150d134bc538c8e6adca3783e3bc31be949071527aa8e3aab7867d1ad2d84a26e10 + languageName: node + linkType: hard + "@babel/helper-get-function-arity@npm:^7.15.4": version: 7.15.4 resolution: "@babel/helper-get-function-arity@npm:7.15.4" @@ -590,6 +629,15 @@ __metadata: languageName: node linkType: hard +"@babel/helper-hoist-variables@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-hoist-variables@npm:7.22.5" + dependencies: + "@babel/types": ^7.22.5 + checksum: 394ca191b4ac908a76e7c50ab52102669efe3a1c277033e49467913c7ed6f7c64d7eacbeabf3bed39ea1f41731e22993f763b1edce0f74ff8563fd1f380d92cc + languageName: node + linkType: hard + "@babel/helper-member-expression-to-functions@npm:^7.15.4": version: 7.15.4 resolution: "@babel/helper-member-expression-to-functions@npm:7.15.4" @@ -899,6 +947,15 @@ __metadata: languageName: node linkType: hard +"@babel/helper-split-export-declaration@npm:^7.22.6": + version: 7.22.6 + resolution: "@babel/helper-split-export-declaration@npm:7.22.6" + dependencies: + "@babel/types": ^7.22.5 + checksum: e141cace583b19d9195f9c2b8e17a3ae913b7ee9b8120246d0f9ca349ca6f03cb2c001fd5ec57488c544347c0bb584afec66c936511e447fd20a360e591ac921 + languageName: node + linkType: hard + "@babel/helper-string-parser@npm:^7.21.5": version: 7.21.5 resolution: "@babel/helper-string-parser@npm:7.21.5" @@ -906,6 +963,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-string-parser@npm:^7.22.5": + version: 7.22.5 + resolution: "@babel/helper-string-parser@npm:7.22.5" + checksum: 836851ca5ec813077bbb303acc992d75a360267aa3b5de7134d220411c852a6f17de7c0d0b8c8dcc0f567f67874c00f4528672b2a4f1bc978a3ada64c8c78467 + languageName: node + linkType: hard + "@babel/helper-validator-identifier@npm:^7.14.5, @babel/helper-validator-identifier@npm:^7.14.9, @babel/helper-validator-identifier@npm:^7.15.7": version: 7.15.7 resolution: "@babel/helper-validator-identifier@npm:7.15.7" @@ -920,6 +984,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-identifier@npm:^7.22.20": + version: 7.22.20 + resolution: "@babel/helper-validator-identifier@npm:7.22.20" + checksum: 136412784d9428266bcdd4d91c32bcf9ff0e8d25534a9d94b044f77fe76bc50f941a90319b05aafd1ec04f7d127cd57a179a3716009ff7f3412ef835ada95bdc + languageName: node + linkType: hard + "@babel/helper-validator-option@npm:^7.14.5": version: 7.14.5 resolution: "@babel/helper-validator-option@npm:7.14.5" @@ -1036,6 +1107,17 @@ __metadata: languageName: node linkType: hard +"@babel/highlight@npm:^7.22.13": + version: 7.22.20 + resolution: "@babel/highlight@npm:7.22.20" + dependencies: + "@babel/helper-validator-identifier": ^7.22.20 + chalk: ^2.4.2 + js-tokens: ^4.0.0 + checksum: 84bd034dca309a5e680083cd827a766780ca63cef37308404f17653d32366ea76262bd2364b2d38776232f2d01b649f26721417d507e8b4b6da3e4e739f6d134 + languageName: node + linkType: hard + "@babel/parser@npm:^7.10.3, @babel/parser@npm:^7.15.4, @babel/parser@npm:^7.15.8": version: 7.15.8 resolution: "@babel/parser@npm:7.15.8" @@ -1045,7 +1127,7 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.16.0, @babel/parser@npm:^7.16.3": +"@babel/parser@npm:^7.16.0": version: 7.16.3 resolution: "@babel/parser@npm:7.16.3" bin: @@ -1054,7 +1136,7 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.20.7, @babel/parser@npm:^7.21.5, @babel/parser@npm:^7.21.8": +"@babel/parser@npm:^7.20.7, @babel/parser@npm:^7.21.8": version: 7.21.8 resolution: "@babel/parser@npm:7.21.8" bin: @@ -1063,6 +1145,15 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.22.15, @babel/parser@npm:^7.23.0": + version: 7.23.0 + resolution: "@babel/parser@npm:7.23.0" + bin: + parser: ./bin/babel-parser.js + checksum: 453fdf8b9e2c2b7d7b02139e0ce003d1af21947bbc03eb350fb248ee335c9b85e4ab41697ddbdd97079698de825a265e45a0846bb2ed47a2c7c1df833f42a354 + languageName: node + linkType: hard + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.16.2": version: 7.16.2 resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.16.2" @@ -3643,55 +3734,32 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.10.3, @babel/traverse@npm:^7.13.0, @babel/traverse@npm:^7.15.4, @babel/traverse@npm:^7.4.5": - version: 7.15.4 - resolution: "@babel/traverse@npm:7.15.4" +"@babel/template@npm:^7.22.15": + version: 7.22.15 + resolution: "@babel/template@npm:7.22.15" dependencies: - "@babel/code-frame": ^7.14.5 - "@babel/generator": ^7.15.4 - "@babel/helper-function-name": ^7.15.4 - "@babel/helper-hoist-variables": ^7.15.4 - "@babel/helper-split-export-declaration": ^7.15.4 - "@babel/parser": ^7.15.4 - "@babel/types": ^7.15.4 - debug: ^4.1.0 - globals: ^11.1.0 - checksum: 831506a92c8ed76dc60504de37663bf5a553d7b1b009a94defc082cddb6c380c5487a1aa9438bcd7b9891a2a72758a63e4f878154aa70699d09b388b1445d774 + "@babel/code-frame": ^7.22.13 + "@babel/parser": ^7.22.15 + "@babel/types": ^7.22.15 + checksum: 1f3e7dcd6c44f5904c184b3f7fe280394b191f2fed819919ffa1e529c259d5b197da8981b6ca491c235aee8dbad4a50b7e31304aa531271cb823a4a24a0dd8fd languageName: node linkType: hard -"@babel/traverse@npm:^7.16.0, @babel/traverse@npm:^7.16.3": - version: 7.16.3 - resolution: "@babel/traverse@npm:7.16.3" +"@babel/traverse@npm:^7.10.3, @babel/traverse@npm:^7.13.0, @babel/traverse@npm:^7.15.4, @babel/traverse@npm:^7.16.0, @babel/traverse@npm:^7.16.3, @babel/traverse@npm:^7.19.0, @babel/traverse@npm:^7.20.5, @babel/traverse@npm:^7.21.5, @babel/traverse@npm:^7.4.5": + version: 7.23.2 + resolution: "@babel/traverse@npm:7.23.2" dependencies: - "@babel/code-frame": ^7.16.0 - "@babel/generator": ^7.16.0 - "@babel/helper-function-name": ^7.16.0 - "@babel/helper-hoist-variables": ^7.16.0 - "@babel/helper-split-export-declaration": ^7.16.0 - "@babel/parser": ^7.16.3 - "@babel/types": ^7.16.0 + "@babel/code-frame": ^7.22.13 + "@babel/generator": ^7.23.0 + "@babel/helper-environment-visitor": ^7.22.20 + "@babel/helper-function-name": ^7.23.0 + "@babel/helper-hoist-variables": ^7.22.5 + "@babel/helper-split-export-declaration": ^7.22.6 + "@babel/parser": ^7.23.0 + "@babel/types": ^7.23.0 debug: ^4.1.0 globals: ^11.1.0 - checksum: abb14857b1104c73124612954865e28f95a86eb6741f35851369b4f9eabc17e394c9aa6f21fba6ce23813592353090d409772be828717cbe5154a5e981a753c1 - languageName: node - linkType: hard - -"@babel/traverse@npm:^7.19.0, @babel/traverse@npm:^7.20.5, @babel/traverse@npm:^7.21.5": - version: 7.21.5 - resolution: "@babel/traverse@npm:7.21.5" - dependencies: - "@babel/code-frame": ^7.21.4 - "@babel/generator": ^7.21.5 - "@babel/helper-environment-visitor": ^7.21.5 - "@babel/helper-function-name": ^7.21.0 - "@babel/helper-hoist-variables": ^7.18.6 - "@babel/helper-split-export-declaration": ^7.18.6 - "@babel/parser": ^7.21.5 - "@babel/types": ^7.21.5 - debug: ^4.1.0 - globals: ^11.1.0 - checksum: b403733fa7d858f0c8e224f0434a6ade641bc469a4f92975363391e796629d5bf53e544761dfe85039aab92d5389ebe7721edb309d7a5bb7df2bf74f37bf9f47 + checksum: 26a1eea0dde41ab99dde8b9773a013a0dc50324e5110a049f5d634e721ff08afffd54940b3974a20308d7952085ac769689369e9127dea655f868c0f6e1ab35d languageName: node linkType: hard @@ -3726,6 +3794,17 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.22.15, @babel/types@npm:^7.22.5, @babel/types@npm:^7.23.0": + version: 7.23.0 + resolution: "@babel/types@npm:7.23.0" + dependencies: + "@babel/helper-string-parser": ^7.22.5 + "@babel/helper-validator-identifier": ^7.22.20 + to-fast-properties: ^2.0.0 + checksum: 215fe04bd7feef79eeb4d33374b39909ce9cad1611c4135a4f7fdf41fe3280594105af6d7094354751514625ea92d0875aba355f53e86a92600f290e77b0e604 + languageName: node + linkType: hard + "@bcoe/v8-coverage@npm:^0.2.3": version: 0.2.3 resolution: "@bcoe/v8-coverage@npm:0.2.3" From 2f9f7aa5f9f1d338e3fd758c7ed6e37c7232ba3b Mon Sep 17 00:00:00 2001 From: BE <137172332+VWSCoronaDashboard29@users.noreply.github.com> Date: Tue, 17 Oct 2023 11:58:10 +0200 Subject: [PATCH 03/19] fix(COR-1819): Fix article layout safari (#4905) Co-authored-by: VWSCoronaDashboard29 --- packages/app/src/components/articles/page-articles-tile.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/app/src/components/articles/page-articles-tile.tsx b/packages/app/src/components/articles/page-articles-tile.tsx index ec80b2a0da..2632c76b9c 100644 --- a/packages/app/src/components/articles/page-articles-tile.tsx +++ b/packages/app/src/components/articles/page-articles-tile.tsx @@ -77,7 +77,6 @@ const ArticleCard = styled(Anchor)` display: flex; flex-direction: column; gap: ${space[2]}; - height: 100%; padding: ${space[3]}; ${Text} { From bc78169d83fa553885dfa3b01c54dd25a6e7b2e3 Mon Sep 17 00:00:00 2001 From: BE <137172332+VWSCoronaDashboard29@users.noreply.github.com> Date: Tue, 17 Oct 2023 11:58:27 +0200 Subject: [PATCH 04/19] fix(COR-1817): New sanity key for updated campaign name (#4904) Co-authored-by: VWSCoronaDashboard29 --- packages/cms/src/lokalize/key-mutations.csv | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/cms/src/lokalize/key-mutations.csv b/packages/cms/src/lokalize/key-mutations.csv index a7c6419a8a..4f77c57842 100644 --- a/packages/cms/src/lokalize/key-mutations.csv +++ b/packages/cms/src/lokalize/key-mutations.csv @@ -1 +1,3 @@ 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,__ From 51f74db025b3a3672c9e16a5f52ddd98450bcc4f Mon Sep 17 00:00:00 2001 From: BE <137172332+VWSCoronaDashboard29@users.noreply.github.com> Date: Mon, 23 Oct 2023 10:48:15 +0200 Subject: [PATCH 05/19] feature/COR-1811 adjust variant page (#4907) * feat(COR-1811): Add kpi section to variants page and update metadataprops * feat(COR-1811): Variants table arrows color * refactor(COR-1811): Refactor functions * feat(COR-1811): Show variants table conditionally * feat(COR-1811): Update schema for archived variants * feat(COR-1811): Regenerate datatypes based on schema update * feat(COR-1811): Archive variants graph * refactor(COR-1811): Refactored variants components * fix: Add parameters to nextJS dev server * refactor: Split useSeriesConfig into seperate file * task: create semi-working prototype for bar chart * feat(COR-1811): Build variant bar chart * feat(COR-1811): Add comment * feat(COR-1811): Update barchart to timeframe * fix(COR-1811): Add differentation between states of interactiveLegends * feat(COR-1811): Calculate total variants in frontend * feat(COR-1811): Refactor keys and add comments * feat(COR-1811): Add key mutations --------- Co-authored-by: VWSCoronaDashboard29 --- packages/app/package.json | 2 +- packages/app/schema/archived_nl/__index.json | 4 + packages/app/schema/archived_nl/variants.json | 86 ++++++++++++++ packages/app/schema/nl/__index.json | 3 +- .../components/kpi/bordered-kpi-section.tsx | 3 +- packages/app/src/components/kpi/types.ts | 1 + packages/app/src/components/metadata.tsx | 9 +- .../stacked-chart/stacked-chart.tsx | 10 +- .../vaccine-campaigns-tile.tsx | 14 +-- .../get-archived-variant-chart-data.ts} | 17 +-- .../get-variant-bar-chart-data.ts | 54 +++++++++ .../get-variant-order-colors.ts | 7 +- .../get-variant-table-data.ts | 15 +-- .../domain/variants/data-selection/index.ts | 4 + .../domain/variants/data-selection/types.ts | 40 +++++++ packages/app/src/domain/variants/index.ts | 4 + .../domain/variants/logic/use-bar-config.ts | 69 +++++++++++ .../variants/logic/use-series-config.ts | 60 ++++++++++ .../logic/use-unreliable-data-annotations.ts | 9 +- .../src/domain/variants/static-props/index.ts | 3 - .../variants-stacked-area-tile.tsx | 53 +-------- .../variants-stacked-area-tile/index.ts | 1 - .../variants-stacked-bar-chart-tile.tsx | 108 ++++++++++++++++++ .../variants-table-tile.tsx | 63 +++++----- .../components/narrow-variants-table.tsx | 2 +- .../components/variant-difference.tsx | 4 +- .../components/variant-name-cell.tsx | 2 +- .../components/variants-table.tsx | 12 +- .../components/wide-variants-table.tsx | 2 +- .../variants/variants-table-tile/index.ts | 1 - .../variants/variants-table-tile/types.ts | 2 +- .../app/src/pages/landelijk/vaccinaties.tsx | 4 +- .../app/src/pages/landelijk/varianten.tsx | 96 +++++++++++++--- packages/cms/src/lokalize/key-mutations.csv | 12 ++ .../cms/src/studio/data/data-structure.ts | 1 + packages/common/src/data-sorting.ts | 53 +++++---- packages/common/src/types/data.ts | 23 +++- 37 files changed, 659 insertions(+), 194 deletions(-) create mode 100644 packages/app/schema/archived_nl/variants.json rename packages/app/src/domain/variants/{static-props/get-variant-chart-data.ts => data-selection/get-archived-variant-chart-data.ts} (79%) create mode 100644 packages/app/src/domain/variants/data-selection/get-variant-bar-chart-data.ts rename packages/app/src/domain/variants/{static-props => data-selection}/get-variant-order-colors.ts (88%) rename packages/app/src/domain/variants/{static-props => data-selection}/get-variant-table-data.ts (83%) create mode 100644 packages/app/src/domain/variants/data-selection/index.ts create mode 100644 packages/app/src/domain/variants/data-selection/types.ts create mode 100644 packages/app/src/domain/variants/index.ts create mode 100644 packages/app/src/domain/variants/logic/use-bar-config.ts create mode 100644 packages/app/src/domain/variants/logic/use-series-config.ts rename packages/app/src/domain/variants/{variants-stacked-area-tile => }/logic/use-unreliable-data-annotations.ts (78%) delete mode 100644 packages/app/src/domain/variants/static-props/index.ts rename packages/app/src/domain/variants/{variants-stacked-area-tile => }/variants-stacked-area-tile.tsx (71%) delete mode 100644 packages/app/src/domain/variants/variants-stacked-area-tile/index.ts create mode 100644 packages/app/src/domain/variants/variants-stacked-bar-chart-tile.tsx rename packages/app/src/domain/variants/{variants-table-tile => }/variants-table-tile.tsx (60%) delete mode 100644 packages/app/src/domain/variants/variants-table-tile/index.ts 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; } From f38f823839e2407bbb53f8a56253ccfc89ddac9d Mon Sep 17 00:00:00 2001 From: BE <137172332+VWSCoronaDashboard29@users.noreply.github.com> Date: Mon, 23 Oct 2023 17:20:39 +0200 Subject: [PATCH 06/19] chore(COR-1830): Rename page and update router and redirects (#4909) * chore(COR-1830): Rename page and update router and redirects * chore(COR-1830): Update vaccines to coronaprik and apply json edits * chore: Update reverserouter links --------- Co-authored-by: VWSCoronaDashboard29 --- packages/app/src/components/sitemap/use-data-sitemap.ts | 4 ++-- packages/app/src/domain/layout/gm-layout.tsx | 2 +- packages/app/src/domain/layout/logic/types.ts | 4 ++-- packages/app/src/domain/layout/logic/use-sidebar.tsx | 2 +- packages/app/src/domain/layout/nl-layout.tsx | 2 +- packages/app/src/next-config/redirects/redirects.js | 6 ++++++ packages/app/src/pages/gemeente/[code]/vaccinaties.tsx | 4 ++-- .../pages/landelijk/{vaccinaties.tsx => de-coronaprik.tsx} | 4 ++-- .../app/src/utils/__tests__/use-reverse-router.spec.tsx | 4 ++-- packages/cms/src/lokalize/key-mutations.csv | 2 ++ packages/common/src/data/reverse-router.ts | 4 ++-- 11 files changed, 23 insertions(+), 15 deletions(-) rename packages/app/src/pages/landelijk/{vaccinaties.tsx => de-coronaprik.tsx} (99%) diff --git a/packages/app/src/components/sitemap/use-data-sitemap.ts b/packages/app/src/components/sitemap/use-data-sitemap.ts index 3dd5793b7c..afd7ff955d 100644 --- a/packages/app/src/components/sitemap/use-data-sitemap.ts +++ b/packages/app/src/components/sitemap/use-data-sitemap.ts @@ -48,8 +48,8 @@ export function useDataSitemap(base: 'nl' | 'gm', code?: string, data?: Pick) => /> ) => )} ) { reverseRouter.gm.vaccinaties(gmcode), isPercentage: true }} + dataOptions={{ getLink: (gmcode) => reverseRouter.gm.deCoronaprik(gmcode), isPercentage: true }} text={{ title: commonTexts.choropleth.choropleth_vaccination_coverage.nl.archived.fully_vaccinated.title, description: commonTexts.choropleth.choropleth_vaccination_coverage.nl.archived.fully_vaccinated.description, @@ -365,7 +365,7 @@ function VaccinationPage(props: StaticProps) { reverseRouter.gm.vaccinaties(gmcode), isPercentage: true }} + dataOptions={{ getLink: (gmcode) => reverseRouter.gm.deCoronaprik(gmcode), isPercentage: true }} text={{ title: commonTexts.choropleth.choropleth_vaccination_coverage.nl.archived.autumn_2022.title, description: commonTexts.choropleth.choropleth_vaccination_coverage.nl.archived.autumn_2022.description, diff --git a/packages/app/src/utils/__tests__/use-reverse-router.spec.tsx b/packages/app/src/utils/__tests__/use-reverse-router.spec.tsx index 2b5b38be59..712ed87f35 100644 --- a/packages/app/src/utils/__tests__/use-reverse-router.spec.tsx +++ b/packages/app/src/utils/__tests__/use-reverse-router.spec.tsx @@ -78,8 +78,8 @@ UseReverseRouter("indexes should 'redirect' to child pages", () => { const nlDiv = result.getByTestId('nl'); const gmDiv = result.getByTestId('gm'); - assert.equal(nlDiv.textContent?.endsWith('/vaccinaties'), true); - assert.equal(gmDiv.textContent?.endsWith('/vaccinaties'), true); + assert.equal(nlDiv.textContent?.endsWith('/deCoronaprik'), true); + assert.equal(gmDiv.textContent?.endsWith('/deCoronaprik'), true); }); UseReverseRouter('GM routes should have the GM code in them', () => { diff --git a/packages/cms/src/lokalize/key-mutations.csv b/packages/cms/src/lokalize/key-mutations.csv index d6e70b0f18..2601065f2e 100644 --- a/packages/cms/src/lokalize/key-mutations.csv +++ b/packages/cms/src/lokalize/key-mutations.csv @@ -13,3 +13,5 @@ timestamp,action,key,document_id,move_to 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,__ +2023-10-23T15:10:02.545Z,add,common.sidebar.metrics.the_corona_vaccine.title,ZkwHqMQjnsmR1ekP50kkn5,__ +2023-10-23T15:10:02.545Z,delete,common.sidebar.metrics.vaccinations.title,wzQp83DAUqyqV3ewG9xYUn,__ diff --git a/packages/common/src/data/reverse-router.ts b/packages/common/src/data/reverse-router.ts index a784920999..74caf6a4dc 100644 --- a/packages/common/src/data/reverse-router.ts +++ b/packages/common/src/data/reverse-router.ts @@ -16,7 +16,7 @@ export function getReverseRouter(isMobile: boolean) { nl: { index: () => (isMobile ? '/landelijk' : reverseRouter.nl.rioolwater()), - vaccinaties: () => '/landelijk/vaccinaties', + deCoronaprik: () => '/landelijk/de-coronaprik', positieveTesten: () => '/landelijk/positieve-testen', infectieradar: () => '/landelijk/infectieradar', besmettelijkeMensen: () => '/landelijk/besmettelijke-mensen', @@ -43,7 +43,7 @@ export function getReverseRouter(isMobile: boolean) { sterfte: (code: string) => `/gemeente/${code}/sterfte`, ziekenhuisopnames: (code: string) => `/gemeente/${code}/ziekenhuis-opnames`, rioolwater: (code: string) => `/gemeente/${code}/rioolwater`, - vaccinaties: (code: string) => `/gemeente/${code}/vaccinaties`, + deCoronaprik: (code: string) => `/gemeente/${code}/de-coronaprik`, }, } as const; From 067ad0f2575ebd8526929d39f0b947d87b8fb4df Mon Sep 17 00:00:00 2001 From: BE <137172332+VWSCoronaDashboard29@users.noreply.github.com> Date: Mon, 23 Oct 2023 17:22:26 +0200 Subject: [PATCH 07/19] feat(COR-1811): Different way of using interactive legend (#4908) Co-authored-by: VWSCoronaDashboard29 --- packages/app/src/components/interactive-legend.tsx | 4 ++-- .../app/src/domain/variants/logic/use-bar-config.ts | 10 ++++++++++ .../app/src/domain/variants/logic/use-series-config.ts | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/app/src/components/interactive-legend.tsx b/packages/app/src/components/interactive-legend.tsx index 1d8731ed73..6d724ccb1a 100644 --- a/packages/app/src/components/interactive-legend.tsx +++ b/packages/app/src/components/interactive-legend.tsx @@ -37,7 +37,7 @@ export function InteractiveLegend({ helpText, selectOptions, selecti const isSelected = selection.includes(item.metricProperty); return ( - + {item.label} {item.shape === 'line' && } {item.shape === 'dashed' && ( @@ -51,7 +51,7 @@ export function InteractiveLegend({ helpText, selectOptions, selecti onToggleItem(item.metricProperty)} aria-label={item.legendAriaLabel} diff --git a/packages/app/src/domain/variants/logic/use-bar-config.ts b/packages/app/src/domain/variants/logic/use-bar-config.ts index 9de1371762..3d95b3bbf7 100644 --- a/packages/app/src/domain/variants/logic/use-bar-config.ts +++ b/packages/app/src/domain/variants/logic/use-bar-config.ts @@ -10,6 +10,16 @@ const extractVariantNamesFromValues = (values: VariantChartValue[]) => { .filter((keyName) => keyName.endsWith('_occurrence')); }; +/** + * Create configuration labels for interactive legend + * @param values + * @param selectedOptions + * @param variantLabels + * @param tooltipLabels + * @param colors + * @param timeframe + * @param today + */ export const useBarConfig = ( values: VariantChartValue[], selectedOptions: (keyof VariantChartValue)[], diff --git a/packages/app/src/domain/variants/logic/use-series-config.ts b/packages/app/src/domain/variants/logic/use-series-config.ts index e0bd1c1e39..aa51e324ec 100644 --- a/packages/app/src/domain/variants/logic/use-series-config.ts +++ b/packages/app/src/domain/variants/logic/use-series-config.ts @@ -31,7 +31,7 @@ export const useSeriesConfig = ( 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; + const variantDynamicLabel = text.variantCodes[variantCode]; // Match appropriate variant color const color = colors.find((variantColors) => variantColors.variant === variantCode)?.color; From 39bf543182ed982ac80d1419cfe02e397e1f13f1 Mon Sep 17 00:00:00 2001 From: BE <137172332+VWSCoronaDashboard29@users.noreply.github.com> Date: Mon, 23 Oct 2023 18:03:08 +0200 Subject: [PATCH 08/19] refactor(COR-1811): Add generic tooltip function (#4910) Co-authored-by: VWSCoronaDashboard29 --- .../variants/logic/reorder-and-filter.ts | 37 +++++++++++++++++ .../variants/variants-stacked-area-tile.tsx | 39 +++--------------- .../variants-stacked-bar-chart-tile.tsx | 41 ++----------------- 3 files changed, 47 insertions(+), 70 deletions(-) create mode 100644 packages/app/src/domain/variants/logic/reorder-and-filter.ts diff --git a/packages/app/src/domain/variants/logic/reorder-and-filter.ts b/packages/app/src/domain/variants/logic/reorder-and-filter.ts new file mode 100644 index 0000000000..7224789670 --- /dev/null +++ b/packages/app/src/domain/variants/logic/reorder-and-filter.ts @@ -0,0 +1,37 @@ +import { isDefined, isPresent } from 'ts-is-present'; +import { VariantChartValue } from '~/domain/variants/data-selection/types'; +import { TooltipData } from '~/components/time-series-chart/components'; + +/** + * 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 + */ +export const reorderAndFilter = (context: TooltipData, selectionOptions: P[]) => { + 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; +}; diff --git a/packages/app/src/domain/variants/variants-stacked-area-tile.tsx b/packages/app/src/domain/variants/variants-stacked-area-tile.tsx index fbc223b6c2..1ba3a953bb 100644 --- a/packages/app/src/domain/variants/variants-stacked-area-tile.tsx +++ b/packages/app/src/domain/variants/variants-stacked-area-tile.tsx @@ -1,6 +1,5 @@ import { TimeframeOption, TimeframeOptionsList } from '@corona-dashboard/common'; import { useMemo, useState } from 'react'; -import { isDefined, isPresent } from 'ts-is-present'; import { Spacer } from '~/components/base'; import { ChartTile } from '~/components/chart-tile'; import { InteractiveLegend } from '~/components/interactive-legend'; @@ -14,6 +13,7 @@ import { space } from '~/style/theme'; 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'; +import { reorderAndFilter } from '~/domain/variants/logic/reorder-and-filter'; const alwaysEnabled: (keyof VariantChartValue)[] = []; @@ -38,6 +38,8 @@ export const VariantsStackedAreaTile = ({ text, values, variantColors, metadata const timespanAnnotations = useUnreliableDataAnnotations(values, text.lagere_betrouwbaarheid); + const hasTwoColumns = list.length === 0 || list.length > 4; + if (timespanAnnotations.length) { staticLegendItems.push({ shape: 'dotted-square', @@ -71,34 +73,9 @@ export const VariantsStackedAreaTile = ({ text, values, variantColors, metadata timespanAnnotations, renderNullAsZero: true, }} - formatTooltip={(context) => { - /** - * Filter out zero values in value object, so it will be invisible in the tooltip. - * When a selection has been made, the zero values will be shown in the tooltip. - */ - const metricAmount = context.config.length; - const totalMetricAmount = seriesConfig.length; - const hasSelectedMetrics = metricAmount !== totalMetricAmount; - - const filteredValues = Object.fromEntries( - Object.entries(context.value).filter(([key, value]) => (key.includes('percentage') ? value !== 0 && isPresent(value) && !isNaN(Number(value)) : value)) - ) as VariantChartValue; - - const reorderContext = { - ...context, - config: [ - // Destructuring so as to not interact with the object directly and eliminate the possibility of introducing inconsistencies - ...context.config.filter((value) => !hasMetricProperty(value) || filteredValues[value.metricProperty] || hasSelectedMetrics), - ].filter(isDefined), - value: !hasSelectedMetrics ? filteredValues : context.value, - }; - - const percentageValuesAmount = Object.keys(reorderContext.value).filter((key) => key.includes('percentage')).length; - - const hasTwoColumns = !hasSelectedMetrics ? percentageValuesAmount > 4 : metricAmount > 4; - - return ; - }} + formatTooltip={(data) => ( + >(data, selectOptions)} hasTwoColumns={hasTwoColumns} /> + )} numGridLines={0} tickValues={[0, 25, 50, 75, 100]} /> @@ -107,10 +84,6 @@ export const VariantsStackedAreaTile = ({ text, values, variantColors, metadata ); }; -const hasMetricProperty = (config: any): config is { metricProperty: string } => { - return 'metricProperty' in config; -}; - const useFilteredSeriesConfig = (seriesConfig: GappedAreaSeriesDefinition[], compareList: (keyof VariantChartValue)[]) => { return useMemo(() => { return seriesConfig.filter((item) => compareList.includes(item.metricProperty) || compareList.length === alwaysEnabled.length); 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 index f124b7c2fc..6dbc183d15 100644 --- a/packages/app/src/domain/variants/variants-stacked-bar-chart-tile.tsx +++ b/packages/app/src/domain/variants/variants-stacked-bar-chart-tile.tsx @@ -7,11 +7,10 @@ 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'; +import { reorderAndFilter } from '~/domain/variants/logic/reorder-and-filter'; interface VariantsStackedBarChartTileProps { title: string; @@ -26,40 +25,6 @@ interface VariantsStackedBarChartTileProps { 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 @@ -101,7 +66,9 @@ export const VariantsStackedBarChartTile = ({ title, description, helpText, tool config={barChartConfig} disableLegend timeframe={variantTimeFrame} - formatTooltip={(data) => } + formatTooltip={(data) => ( + >(data, selectionOptions)} hasTwoColumns={hasTwoColumns} /> + )} /> ); From b7ba6d10292305d4971d14f106bfd45f72b640c5 Mon Sep 17 00:00:00 2001 From: BE <137172332+VWSCoronaDashboard29@users.noreply.github.com> Date: Tue, 24 Oct 2023 13:32:09 +0200 Subject: [PATCH 09/19] Feature/cor 1811 update variant data (#4911) * feat(COR-1811): Refactor, update default timeframe, update totalVariants * feat(COR-1811): Replace stackedChart with TimeSeries chart * chore: add key mutations --------- Co-authored-by: VWSCoronaDashboard29 --- .../get-variant-bar-chart-data.ts | 4 +- .../variants/logic/reorder-and-filter.ts | 4 +- .../domain/variants/logic/use-bar-config.ts | 35 ++++++------- .../variants-stacked-bar-chart-tile.tsx | 50 ++++++++++++------- .../app/src/pages/landelijk/varianten.tsx | 10 ++-- packages/cms/src/lokalize/key-mutations.csv | 6 +++ 6 files changed, 62 insertions(+), 47 deletions(-) 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 index 04f31e1605..64cd45edcb 100644 --- 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 @@ -28,8 +28,8 @@ export function getVariantBarChartData(variants: NlVariants) { return EMPTY_VALUES; } - const values = firstVariantInList.values.map((value, index) => { - const item = { + const values: VariantChartValue[] = firstVariantInList.values.map((value, index) => { + const item: VariantChartValue = { is_reliable: true, date_start_unix: value.date_start_unix, date_end_unix: value.date_end_unix, diff --git a/packages/app/src/domain/variants/logic/reorder-and-filter.ts b/packages/app/src/domain/variants/logic/reorder-and-filter.ts index 7224789670..983bb63249 100644 --- a/packages/app/src/domain/variants/logic/reorder-and-filter.ts +++ b/packages/app/src/domain/variants/logic/reorder-and-filter.ts @@ -17,9 +17,7 @@ const hasMetricProperty = (config: any): config is { metricProperty: string } => * @param selectionOptions - Currently selected variants */ export const reorderAndFilter = (context: TooltipData, selectionOptions: P[]) => { - 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. + const hasSelectedMetrics = context.config.length !== selectionOptions.length; // 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( diff --git a/packages/app/src/domain/variants/logic/use-bar-config.ts b/packages/app/src/domain/variants/logic/use-bar-config.ts index 3d95b3bbf7..7b559dc156 100644 --- a/packages/app/src/domain/variants/logic/use-bar-config.ts +++ b/packages/app/src/domain/variants/logic/use-bar-config.ts @@ -1,7 +1,8 @@ -import { ColorMatch, VariantChartValue, StackedBarConfig, VariantDynamicLabels, VariantsOverTimeGraphText } from '~/domain/variants/data-selection/types'; +import { ColorMatch, VariantChartValue, 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'; +import { BarSeriesDefinition } from '~/components/time-series-chart/logic'; const extractVariantNamesFromValues = (values: VariantChartValue[]) => { return values @@ -12,17 +13,15 @@ const extractVariantNamesFromValues = (values: VariantChartValue[]) => { /** * Create configuration labels for interactive legend - * @param values - * @param selectedOptions - * @param variantLabels - * @param tooltipLabels - * @param colors - * @param timeframe - * @param today + * @param values - Chart data + * @param variantLabels - Mnemonic labels for variants + * @param tooltipLabels - SiteText for other variants + * @param colors - Colors for variants + * @param timeframe - Selected timeframe + * @param today - Date of today */ export const useBarConfig = ( values: VariantChartValue[], - selectedOptions: (keyof VariantChartValue)[], variantLabels: VariantDynamicLabels, tooltipLabels: VariantsOverTimeGraphText, colors: ColorMatch[], @@ -44,7 +43,7 @@ export const useBarConfig = ( .filter((keyName) => activeVariantsInTimeframeNames.includes(keyName)) .reverse(); - const barChartConfig: StackedBarConfig[] = []; + const barChartConfig: BarSeriesDefinition[] = []; listOfVariantCodes.forEach((variantKey) => { const variantCodeName = variantKey.split('_').slice(0, -1).join('_'); @@ -57,23 +56,19 @@ export const useBarConfig = ( if (variantDynamicLabel) { const barChartConfigEntry = { + type: 'bar', metricProperty: variantMetricPropertyName, color: color, label: variantDynamicLabel, + fillOpacity: 1, shape: 'gapped-area', + hideInLegend: true, }; - barChartConfig.push(barChartConfigEntry as StackedBarConfig); + barChartConfig.push(barChartConfigEntry as BarSeriesDefinition); } }); - 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]); + return barChartConfig; + }, [values, tooltipLabels.tooltip_labels.other_percentage, variantLabels, colors, timeframe, today]); }; 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 index 6dbc183d15..aad029ebbb 100644 --- a/packages/app/src/domain/variants/variants-stacked-bar-chart-tile.tsx +++ b/packages/app/src/domain/variants/variants-stacked-bar-chart-tile.tsx @@ -1,21 +1,21 @@ -import { ChartTile, MetadataProps } from '~/components'; +import { ChartTile, MetadataProps, TimeSeriesChart } from '~/components'; import { Spacer } from '~/components/base'; -import { TimeframeOption, TimeframeOptionsList } from '@corona-dashboard/common'; +import { DAY_IN_SECONDS, 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 { ColorMatch, VariantChartValue, VariantDynamicLabels, VariantsOverTimeGraphText } from '~/domain/variants/data-selection/types'; import { useBarConfig } from '~/domain/variants/logic/use-bar-config'; -import { InteractiveLegend } from '~/components/interactive-legend'; +import { InteractiveLegend, SelectOption } from '~/components/interactive-legend'; import { useList } from '~/utils/use-list'; import { TooltipSeriesList } from '~/components/time-series-chart/components/tooltip/tooltip-series-list'; import { space } from '~/style/theme'; import { useCurrentDate } from '~/utils/current-date-context'; import { reorderAndFilter } from '~/domain/variants/logic/reorder-and-filter'; +import { getBoundaryDateStartUnix } from '~/utils'; +import { useIntl } from '~/intl'; interface VariantsStackedBarChartTileProps { title: string; description: string; - helpText: string; values: VariantChartValue[]; tooltipLabels: VariantsOverTimeGraphText; variantLabels: VariantDynamicLabels; @@ -36,14 +36,20 @@ const alwaysEnabled: (keyof VariantChartValue)[] = []; * @param metadata - Metadata block * @constructor */ -export const VariantsStackedBarChartTile = ({ title, description, helpText, tooltipLabels, values, variantLabels, variantColors, metadata }: VariantsStackedBarChartTileProps) => { +export const VariantsStackedBarChartTile = ({ title, description, tooltipLabels, values, variantLabels, variantColors, metadata }: VariantsStackedBarChartTileProps) => { const today = useCurrentDate(); - + const { commonTexts } = useIntl(); const { list, toggle, clear } = useList(alwaysEnabled); + const [variantTimeFrame, setVariantTimeFrame] = useState(TimeframeOption.THIRTY_DAYS); + const barSeriesConfig = useBarConfig(values, variantLabels, tooltipLabels, variantColors, variantTimeFrame, today); + + const text = commonTexts.variants_page; - const [variantTimeFrame, setVariantTimeFrame] = useState(TimeframeOption.THREE_MONTHS); + const interactiveLegendOptions: SelectOption[] = barSeriesConfig; - const [barChartConfig, selectionOptions] = useBarConfig(values, list, variantLabels, tooltipLabels, variantColors, variantTimeFrame, today); + const filteredBarConfig = barSeriesConfig.filter((configItem) => list.includes(configItem.metricProperty) || list.length === 0); + + const underReportedDateStart = getBoundaryDateStartUnix(values, 1); const hasTwoColumns = list.length === 0 || list.length > 4; @@ -53,22 +59,30 @@ export const VariantsStackedBarChartTile = ({ title, description, helpText, tool description={description} metadata={metadata} timeframeOptions={TimeframeOptionsList} - timeframeInitialValue={TimeframeOption.THREE_MONTHS} + timeframeInitialValue={TimeframeOption.THIRTY_DAYS} onSelectTimeframe={setVariantTimeFrame} > - + - ( - >(data, selectionOptions)} hasTwoColumns={hasTwoColumns} /> - )} + formatTooltip={(data) => (data, interactiveLegendOptions)} hasTwoColumns={hasTwoColumns} />} + dataOptions={{ + timespanAnnotations: [ + { + start: underReportedDateStart + DAY_IN_SECONDS / 2, + end: Infinity, + label: text.bar_chart_legend_inaccurate, + shortLabel: text.tooltip_labels.innacurate, + }, + ], + }} /> ); diff --git a/packages/app/src/pages/landelijk/varianten.tsx b/packages/app/src/pages/landelijk/varianten.tsx index 0e569c9728..d01e4ab58e 100644 --- a/packages/app/src/pages/landelijk/varianten.tsx +++ b/packages/app/src/pages/landelijk/varianten.tsx @@ -21,6 +21,7 @@ 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'; +import { NlVariantsVariant } from '@corona-dashboard/common'; const pageMetrics = ['variants', 'named_difference']; @@ -75,7 +76,6 @@ export default function CovidVariantenPage(props: StaticProps(pageText, selectLokalizeTexts); - const [isArchivedContentShown, setIsArchivedContentShown] = useState(false); const metadata = { @@ -86,9 +86,12 @@ 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) + ? data.variants!.values.reduce( + (accumulator, currentVariant: NlVariantsVariant) => + currentVariant.last_value.occurrence > 0 && currentVariant.variant_code !== 'other_variants' ? 1 + accumulator : accumulator, + 0 + ) : NaN; const sampleThresholdPassed = data.variants ? data.variants!.values[0].last_value.sample_size > 100 : false; @@ -154,7 +157,6 @@ export default function CovidVariantenPage(props: StaticProps Date: Tue, 24 Oct 2023 15:27:48 +0200 Subject: [PATCH 10/19] fix(COR-1812): Remove date ranges from last zkh graphs (#4912) Co-authored-by: VWSCoronaDashboard29 --- packages/app/src/pages/landelijk/varianten.tsx | 11 ++--------- .../src/pages/landelijk/ziekenhuizen-in-beeld.tsx | 12 +++++++----- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/packages/app/src/pages/landelijk/varianten.tsx b/packages/app/src/pages/landelijk/varianten.tsx index d01e4ab58e..e1096fd3c6 100644 --- a/packages/app/src/pages/landelijk/varianten.tsx +++ b/packages/app/src/pages/landelijk/varianten.tsx @@ -172,11 +172,7 @@ export default function CovidVariantenPage(props: StaticProps {archivedVariantChart && variantLabels && ( ) => { const hospitalLastValue = getLastFilledValue(data.hospital_lcps); const icuLastValue = getLastFilledValue(data.intensive_care_lcps); - const valuesWithoutDateRange = data.hospital_lcps.values.map((value) => ({ ...value, date_end_unix: undefined, date_start_unix: undefined })); + const lcpsHospitalWithoutRange = data.hospital_lcps.values.map((value) => ({ ...value, date_end_unix: undefined, date_start_unix: undefined })); + const lcpsICWithoutRange = data.intensive_care_lcps.values.map((value) => ({ ...value, date_end_unix: undefined, date_start_unix: undefined })); const lastInsertionDateOfPage = getLastInsertionDateOfPage(data, pageMetrics); @@ -173,7 +174,7 @@ const HospitalsAndCarePage = (props: StaticProps) => { accessibility={{ key: 'hospital_beds_occupied_over_time_chart', }} - values={valuesWithoutDateRange} + values={lcpsHospitalWithoutRange} timeframe={hospitalBedsOccupiedOverTimeTimeframe} forceLegend seriesConfig={[ @@ -225,7 +226,7 @@ const HospitalsAndCarePage = (props: StaticProps) => { accessibility={{ key: 'intensive_care_beds_occupied_over_time_chart', }} - values={data.intensive_care_lcps.values} + values={lcpsICWithoutRange} timeframe={intensiveCareBedsTimeframe} forceLegend seriesConfig={[ @@ -254,6 +255,7 @@ const HospitalsAndCarePage = (props: StaticProps) => { }, ], timelineEvents: getTimelineEvents(content.elements.timeSeries, 'intensive_care_lcps', 'beds_occupied_covid'), + useDatesAsRange: false, }} /> @@ -296,7 +298,7 @@ const HospitalsAndCarePage = (props: StaticProps) => { accessibility={{ key: 'hospital_patient_influx_over_time_chart', }} - values={trimLeadingNullValues(valuesWithoutDateRange, 'influx_covid_patients')} + values={trimLeadingNullValues(lcpsHospitalWithoutRange, 'influx_covid_patients')} timeframe={hospitalPatientInfluxOverTimeTimeframe} seriesConfig={[ { @@ -337,7 +339,7 @@ const HospitalsAndCarePage = (props: StaticProps) => { accessibility={{ key: 'intensive_care_patient_influx_over_time_chart', }} - values={trimLeadingNullValues(data.intensive_care_lcps.values, 'influx_covid_patients')} + values={trimLeadingNullValues(lcpsICWithoutRange, 'influx_covid_patients')} timeframe={intensiveCarePatientInfluxOverTimeTimeframe} seriesConfig={[ { From eed5ef2788a6e4f0aba7f801fa0a8348d58316bb Mon Sep 17 00:00:00 2001 From: BE <137172332+VWSCoronaDashboard29@users.noreply.github.com> Date: Fri, 27 Oct 2023 09:22:10 +0200 Subject: [PATCH 11/19] fix: Set node version number to 18.18.2 (#4915) Co-authored-by: VWSCoronaDashboard29 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index a8afc09901..917d11dc45 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Install dependencies only when needed -FROM node:lts-alpine AS deps +FROM node:18.18.2-alpine AS deps ENV NODE_ENV="production" ENV NEXT_TELEMETRY_DISABLED=1 From 284c981ae4d45443d3e4dcdc97caa3728d46f100 Mon Sep 17 00:00:00 2001 From: BE <137172332+VWSCoronaDashboard29@users.noreply.github.com> Date: Fri, 27 Oct 2023 12:01:39 +0200 Subject: [PATCH 12/19] feat(COR-1811): Implement feedback from testing (#4917) Co-authored-by: VWSCoronaDashboard29 --- .../get-archived-variant-chart-data.ts | 4 +-- .../domain/variants/logic/use-bar-config.ts | 10 +++---- .../variants-stacked-bar-chart-tile.tsx | 26 +++++-------------- .../app/src/pages/landelijk/varianten.tsx | 8 +++--- 4 files changed, 16 insertions(+), 32 deletions(-) diff --git a/packages/app/src/domain/variants/data-selection/get-archived-variant-chart-data.ts b/packages/app/src/domain/variants/data-selection/get-archived-variant-chart-data.ts index 586fe5bd97..a70db0a7d1 100644 --- a/packages/app/src/domain/variants/data-selection/get-archived-variant-chart-data.ts +++ b/packages/app/src/domain/variants/data-selection/get-archived-variant-chart-data.ts @@ -4,7 +4,7 @@ import { VariantChartValue } from '~/domain/variants/data-selection/types'; const EMPTY_VALUES = { archivedVariantChart: null, - dates: { + archivedDates: { date_of_report_unix: 0, date_start_unix: 0, date_end_unix: 0, @@ -45,7 +45,7 @@ export function getArchivedVariantChartData(variants: ArchivedNlVariants | undef return { archivedVariantChart: values, - dates: { + archivedDates: { 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, diff --git a/packages/app/src/domain/variants/logic/use-bar-config.ts b/packages/app/src/domain/variants/logic/use-bar-config.ts index 7b559dc156..edef57756e 100644 --- a/packages/app/src/domain/variants/logic/use-bar-config.ts +++ b/packages/app/src/domain/variants/logic/use-bar-config.ts @@ -1,8 +1,7 @@ -import { ColorMatch, VariantChartValue, VariantDynamicLabels, VariantsOverTimeGraphText } from '~/domain/variants/data-selection/types'; +import { ColorMatch, StackedBarConfig, VariantChartValue, 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'; -import { BarSeriesDefinition } from '~/components/time-series-chart/logic'; const extractVariantNamesFromValues = (values: VariantChartValue[]) => { return values @@ -43,7 +42,7 @@ export const useBarConfig = ( .filter((keyName) => activeVariantsInTimeframeNames.includes(keyName)) .reverse(); - const barChartConfig: BarSeriesDefinition[] = []; + const barChartConfig: StackedBarConfig[] = []; listOfVariantCodes.forEach((variantKey) => { const variantCodeName = variantKey.split('_').slice(0, -1).join('_'); @@ -56,16 +55,13 @@ export const useBarConfig = ( if (variantDynamicLabel) { const barChartConfigEntry = { - type: 'bar', metricProperty: variantMetricPropertyName, color: color, label: variantDynamicLabel, - fillOpacity: 1, shape: 'gapped-area', - hideInLegend: true, }; - barChartConfig.push(barChartConfigEntry as BarSeriesDefinition); + barChartConfig.push(barChartConfigEntry as StackedBarConfig); } }); 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 index aad029ebbb..87abe2c2a3 100644 --- a/packages/app/src/domain/variants/variants-stacked-bar-chart-tile.tsx +++ b/packages/app/src/domain/variants/variants-stacked-bar-chart-tile.tsx @@ -1,6 +1,6 @@ -import { ChartTile, MetadataProps, TimeSeriesChart } from '~/components'; +import { ChartTile, MetadataProps } from '~/components'; import { Spacer } from '~/components/base'; -import { DAY_IN_SECONDS, TimeframeOption, TimeframeOptionsList } from '@corona-dashboard/common'; +import { TimeframeOption, TimeframeOptionsList } from '@corona-dashboard/common'; import { useState } from 'react'; import { ColorMatch, VariantChartValue, VariantDynamicLabels, VariantsOverTimeGraphText } from '~/domain/variants/data-selection/types'; import { useBarConfig } from '~/domain/variants/logic/use-bar-config'; @@ -10,8 +10,8 @@ import { TooltipSeriesList } from '~/components/time-series-chart/components/too import { space } from '~/style/theme'; import { useCurrentDate } from '~/utils/current-date-context'; import { reorderAndFilter } from '~/domain/variants/logic/reorder-and-filter'; -import { getBoundaryDateStartUnix } from '~/utils'; import { useIntl } from '~/intl'; +import { StackedBarTooltipData, StackedChart } from '~/components/stacked-chart'; interface VariantsStackedBarChartTileProps { title: string; @@ -49,8 +49,6 @@ export const VariantsStackedBarChartTile = ({ title, description, tooltipLabels, const filteredBarConfig = barSeriesConfig.filter((configItem) => list.includes(configItem.metricProperty) || list.length === 0); - const underReportedDateStart = getBoundaryDateStartUnix(values, 1); - const hasTwoColumns = list.length === 0 || list.length > 4; return ( @@ -64,25 +62,15 @@ export const VariantsStackedBarChartTile = ({ title, description, tooltipLabels, > - (data, interactiveLegendOptions)} hasTwoColumns={hasTwoColumns} />} - dataOptions={{ - timespanAnnotations: [ - { - start: underReportedDateStart + DAY_IN_SECONDS / 2, - end: Infinity, - label: text.bar_chart_legend_inaccurate, - shortLabel: text.tooltip_labels.innacurate, - }, - ], - }} + disableLegend + formatTooltip={(data) => (data, interactiveLegendOptions)} hasTwoColumns={hasTwoColumns} />} /> ); diff --git a/packages/app/src/pages/landelijk/varianten.tsx b/packages/app/src/pages/landelijk/varianten.tsx index e1096fd3c6..7a8ef646a7 100644 --- a/packages/app/src/pages/landelijk/varianten.tsx +++ b/packages/app/src/pages/landelijk/varianten.tsx @@ -117,8 +117,8 @@ export default function CovidVariantenPage(props: StaticProps Date: Fri, 27 Oct 2023 12:53:44 +0200 Subject: [PATCH 13/19] fix(COR-1830): Apply renaming to GM level too (#4918) Co-authored-by: VWSCoronaDashboard29 --- .../pages/gemeente/[code]/{vaccinaties.tsx => de-coronaprik.tsx} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/app/src/pages/gemeente/[code]/{vaccinaties.tsx => de-coronaprik.tsx} (100%) diff --git a/packages/app/src/pages/gemeente/[code]/vaccinaties.tsx b/packages/app/src/pages/gemeente/[code]/de-coronaprik.tsx similarity index 100% rename from packages/app/src/pages/gemeente/[code]/vaccinaties.tsx rename to packages/app/src/pages/gemeente/[code]/de-coronaprik.tsx From 2a1234fa92fc01c0dc2f3f5819f73dd25d84c1b1 Mon Sep 17 00:00:00 2001 From: BE <137172332+VWSCoronaDashboard29@users.noreply.github.com> Date: Fri, 27 Oct 2023 16:24:05 +0200 Subject: [PATCH 14/19] Feature/cor 1815 aantal deelnemers infectieradar (#4920) * feat(COR-1815): Add KPI tile and update schemas * feat(COR-1815): Add texts --------- Co-authored-by: VWSCoronaDashboard29 --- packages/app/schema/nl/__difference.json | 6 +++- packages/app/schema/nl/self_test_overall.json | 5 ++- .../app/src/pages/landelijk/infectieradar.tsx | 31 ++++++++++++++++++- packages/cms/src/lokalize/key-mutations.csv | 4 +++ packages/common/src/types/data.ts | 2 ++ 5 files changed, 45 insertions(+), 3 deletions(-) diff --git a/packages/app/schema/nl/__difference.json b/packages/app/schema/nl/__difference.json index 0f9a5ff65e..a96261ba71 100644 --- a/packages/app/schema/nl/__difference.json +++ b/packages/app/schema/nl/__difference.json @@ -26,6 +26,9 @@ }, "vulnerable_hospital_admissions": { "$ref": "#/definitions/diff_integer" + }, + "self_test_overall": { + "$ref": "#/definitions/diff_decimal" } }, "required": [ @@ -34,7 +37,8 @@ "infectious_people__estimate", "intensive_care_nice__admissions_on_date_of_reporting_moving_average", "intensive_care_lcps__beds_occupied_covid", - "sewer__average" + "sewer__average", + "self_test_overall" ], "additionalProperties": false, "definitions": { diff --git a/packages/app/schema/nl/self_test_overall.json b/packages/app/schema/nl/self_test_overall.json index 6cdc0b9110..51ac345a5f 100644 --- a/packages/app/schema/nl/self_test_overall.json +++ b/packages/app/schema/nl/self_test_overall.json @@ -3,12 +3,15 @@ "value": { "title": "nl_self_test_overall_value", "type": "object", - "required": ["infected_percentage", "date_start_unix", "date_end_unix", "date_of_insertion_unix"], + "required": ["infected_percentage", "n_participants_total_unfiltered", "date_start_unix", "date_end_unix", "date_of_insertion_unix"], "additionalProperties": false, "properties": { "infected_percentage": { "type": ["number", "null"] }, + "n_participants_total_unfiltered": { + "type": ["number"] + }, "date_start_unix": { "type": "integer" }, diff --git a/packages/app/src/pages/landelijk/infectieradar.tsx b/packages/app/src/pages/landelijk/infectieradar.tsx index d36eb46400..ebc68fda33 100644 --- a/packages/app/src/pages/landelijk/infectieradar.tsx +++ b/packages/app/src/pages/landelijk/infectieradar.tsx @@ -22,6 +22,8 @@ import { ArticleParts, 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 { KpiTile, KpiValue, TwoKpiSection } from '~/components'; +import { replaceVariablesInText } from '~/utils'; const pageMetrics = ['self_test_overall', 'infection_radar_symptoms_per_age_group']; @@ -35,7 +37,7 @@ type LokalizeTexts = ReturnType; export const getStaticProps = createGetStaticProps( ({ locale }: { locale: keyof Languages }) => getLokalizeTexts(selectLokalizeTexts, locale), getLastGeneratedDate, - selectNlData('self_test_overall', 'infectionradar_symptoms_trend_per_age_group_weekly'), + selectNlData('difference.self_test_overall', 'self_test_overall', 'infectionradar_symptoms_trend_per_age_group_weekly'), async (context: GetStaticPropsContext) => { const { content } = await createGetContent<{ parts: PagePartQueryResult; @@ -69,6 +71,8 @@ const InfectionRadar = (props: StaticProps) => { const { metadataTexts, textNl } = useDynamicLokalizeTexts(pageText, selectLokalizeTexts); + const totalInfectedPercentage = data.self_test_overall.last_value.infected_percentage ? data.self_test_overall.last_value.infected_percentage : 0; + const metadata = { ...metadataTexts, title: textNl.metadata.title, @@ -102,6 +106,31 @@ const InfectionRadar = (props: StaticProps) => { })} /> + + + + + + + + + Date: Mon, 30 Oct 2023 14:02:47 +0100 Subject: [PATCH 15/19] fix(COR-1811): Minor fixes (#4921) Co-authored-by: VWSCoronaDashboard29 --- .../components/variant-difference.tsx | 4 ++-- packages/app/src/pages/landelijk/varianten.tsx | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) 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 c8e5d50314..2b5f610b80 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 @@ -35,7 +35,7 @@ export const VariantDifference = ({ value, text, isWideTable }: VariantDifferenc renderingValue: ( - {formatPercentage(value.difference, options)} {text.verschil.meer} + {formatPercentage(value.difference, options)}% {text.verschil.meer} ), }, @@ -44,7 +44,7 @@ export const VariantDifference = ({ value, text, isWideTable }: VariantDifferenc renderingValue: ( - {formatPercentage(-value.difference, options)} {text.verschil.minder} + {formatPercentage(-value.difference, options)}% {text.verschil.minder} ), }, diff --git a/packages/app/src/pages/landelijk/varianten.tsx b/packages/app/src/pages/landelijk/varianten.tsx index 7a8ef646a7..0b77ff71c5 100644 --- a/packages/app/src/pages/landelijk/varianten.tsx +++ b/packages/app/src/pages/landelijk/varianten.tsx @@ -35,6 +35,7 @@ type LokalizeTexts = ReturnType; export const getStaticProps = createGetStaticProps( ({ locale }: { locale: keyof Languages }) => getLokalizeTexts(selectLokalizeTexts, locale), selectNlData('variants', 'named_difference'), + selectArchivedNlData('variants_archived_20231101'), getLastGeneratedDate, () => { const data = selectNlData('variants', 'named_difference')(); @@ -72,7 +73,18 @@ export const getStaticProps = createGetStaticProps( ); export default function CovidVariantenPage(props: StaticProps) { - const { pageText, selectedNlData: data, lastGenerated, content, variantTable, variantChart, archivedVariantChart, variantColors, dates } = props; + const { + pageText, + selectedNlData: data, + selectedArchivedNlData: archivedData, + lastGenerated, + content, + variantTable, + variantChart, + archivedVariantChart, + variantColors, + dates, + } = props; const { commonTexts, locale } = useIntl(); const { metadataTexts, textNl } = useDynamicLokalizeTexts(pageText, selectLokalizeTexts); @@ -207,7 +219,7 @@ export default function CovidVariantenPage(props: StaticProps From a68ed3e176157b55deb8858303941dd5bfa658a2 Mon Sep 17 00:00:00 2001 From: BE <137172332+VWSCoronaDashboard29@users.noreply.github.com> Date: Mon, 30 Oct 2023 16:47:39 +0100 Subject: [PATCH 16/19] fix: Remove percentage and whitespace (#4922) Co-authored-by: VWSCoronaDashboard29 --- packages/app/src/components/stacked-chart/stacked-chart.tsx | 4 ++-- .../variants-table-tile/components/variant-difference.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/app/src/components/stacked-chart/stacked-chart.tsx b/packages/app/src/components/stacked-chart/stacked-chart.tsx index cbb52b4407..fa147a30e7 100644 --- a/packages/app/src/components/stacked-chart/stacked-chart.tsx +++ b/packages/app/src/components/stacked-chart/stacked-chart.tsx @@ -419,7 +419,7 @@ export function StackedChart(props: StackedChartProp * negative height is not allowed. */ y={bar.y + (isTinyScreen ? 1 : 2)} - height={Math.max(0, bar.height - (isTinyScreen ? 1 : 2))} + height={Math.max(0, bar.height)} width={bar.width} fill={fillColor} onMouseLeave={handleHoverWithBar} @@ -436,7 +436,7 @@ export function StackedChart(props: StackedChartProp * negative height is not allowed. */ y={bar.y + (isTinyScreen ? 1 : 2)} - height={Math.max(0, bar.height - (isTinyScreen ? 1 : 2))} + height={Math.max(0, bar.height)} width={bar.width} fill={breakpoints.lg ? 'url(#pattern-hatched)' : 'url(#pattern-hatched-small)'} onMouseLeave={handleHoverWithBar} 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 2b5f610b80..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 @@ -35,7 +35,7 @@ export const VariantDifference = ({ value, text, isWideTable }: VariantDifferenc renderingValue: ( - {formatPercentage(value.difference, options)}% {text.verschil.meer} + {formatPercentage(value.difference, options)} {text.verschil.meer} ), }, @@ -44,7 +44,7 @@ export const VariantDifference = ({ value, text, isWideTable }: VariantDifferenc renderingValue: ( - {formatPercentage(-value.difference, options)}% {text.verschil.minder} + {formatPercentage(-value.difference, options)} {text.verschil.minder} ), }, From a04395779939605181da52dd39e636675f16decf Mon Sep 17 00:00:00 2001 From: BE <137172332+VWSCoronaDashboard29@users.noreply.github.com> Date: Mon, 30 Oct 2023 21:55:21 +0100 Subject: [PATCH 17/19] Feat/cor 1811 bar chart timeseries (#4923) * feat(COR-1811): Create stacked bar timeseries component * feat(COR-1811): Add stackedBarTrendIcon --------- Co-authored-by: VWSCoronaDashboard29 --- .../components/series-icon.tsx | 54 ++---- .../time-series-chart/components/series.tsx | 53 ++--- .../components/stacked-bar-trend.tsx | 127 ++++++++++++ .../time-series-chart/logic/hover-state.ts | 183 +++++++----------- .../time-series-chart/logic/series.ts | 46 +++++ .../domain/variants/logic/use-bar-config.ts | 9 +- .../variants-stacked-bar-chart-tile.tsx | 9 +- 7 files changed, 279 insertions(+), 202 deletions(-) create mode 100644 packages/app/src/components/time-series-chart/components/stacked-bar-trend.tsx diff --git a/packages/app/src/components/time-series-chart/components/series-icon.tsx b/packages/app/src/components/time-series-chart/components/series-icon.tsx index 589f3c3f60..aedc83a125 100644 --- a/packages/app/src/components/time-series-chart/components/series-icon.tsx +++ b/packages/app/src/components/time-series-chart/components/series-icon.tsx @@ -8,6 +8,7 @@ import { ScatterPlotIcon } from './scatter-plot'; import { RangeTrendIcon } from './range-trend'; import { SplitAreaTrendIcon } from './split-area-trend'; import { StackedAreaTrendIcon } from './stacked-area-trend'; +import { StackedBarTrendIcon } from '~/components/time-series-chart/components/stacked-bar-trend'; interface SeriesIconProps { config: SeriesConfig[number]; @@ -20,47 +21,25 @@ interface SeriesIconProps { value?: number | null; } -export function SeriesIcon({ - config, - value, -}: SeriesIconProps) { +export function SeriesIcon({ config, value }: SeriesIconProps) { switch (config.type) { case 'line': case 'gapped-line': - return ( - - ); + return ; case 'scatter-plot': return ; case 'range': - return ( - - ); + return ; case 'area': case 'gapped-area': - return ( - - ); + return ; case 'stacked-area': case 'gapped-stacked-area': - return ( - - ); + return ; case 'bar': - return ( - - ); + return ; + case 'stacked-bar': + return ; case 'split-area': /** * Here we return the icon even if there is no value, because it @@ -71,20 +50,9 @@ export function SeriesIcon({ * * @TODO Possibly we want this behavior for split-bar as well... */ - return ( - - ); + return ; case 'split-bar': - return isPresent(value) ? ( - - ) : null; + return isPresent(value) ? : null; default: return null; } diff --git a/packages/app/src/components/time-series-chart/components/series.tsx b/packages/app/src/components/time-series-chart/components/series.tsx index 4ad196bb1d..c19e3b9e6d 100644 --- a/packages/app/src/components/time-series-chart/components/series.tsx +++ b/packages/app/src/components/time-series-chart/components/series.tsx @@ -2,23 +2,14 @@ import { TimestampedValue } from '@corona-dashboard/common'; import { ScaleLinear } from 'd3-scale'; import { memo } from 'react'; import { AreaTrend, BarTrend, LineTrend, ScatterPlot, RangeTrend } from '.'; -import { - Bounds, - GetX, - GetY, - GetY0, - GetY1, - SeriesConfig, - SeriesDoubleValue, - SeriesList, - SeriesSingleValue, -} from '../logic'; +import { Bounds, GetX, GetY, GetY0, GetY1, SeriesConfig, SeriesDoubleValue, SeriesList, SeriesSingleValue } from '../logic'; import { GappedAreaTrend } from './gapped-area-trend'; import { GappedLinedTrend } from './gapped-line-trend'; import { GappedStackedAreaTrend } from './gapped-stacked-area-trend'; import { SplitAreaTrend } from './split-area-trend'; import { SplitBarTrend } from './split-bar-trend'; import { StackedAreaTrend } from './stacked-area-trend'; +import { StackedBarTrend } from '~/components/time-series-chart/components/stacked-bar-trend'; interface SeriesProps { seriesConfig: SeriesConfig; @@ -42,18 +33,7 @@ interface SeriesProps { export const Series = memo(SeriesUnmemoized) as typeof SeriesUnmemoized; -function SeriesUnmemoized({ - seriesConfig, - seriesList, - getX, - getY, - getY0, - getY1, - yScale, - bounds, - chartId, - seriesMax, -}: SeriesProps) { +function SeriesUnmemoized({ seriesConfig, seriesList, getX, getY, getY0, getY1, yScale, bounds, chartId, seriesMax }: SeriesProps) { return ( <> {seriesList @@ -94,16 +74,7 @@ function SeriesUnmemoized({ /> ); case 'scatter-plot': - return ( - - ); + return ; case 'area': return ( ({ seriesMax={seriesMax} /> ); + case 'stacked-bar': + return ( + + ); case 'split-bar': return ( series.filter((x) => isPresent(x.__value_a) && isPresent(x.__value_b)), [series]); + + const xScale = useMemo( + () => + scaleBand({ + range: [0, bounds.width], + round: true, + domain: series.map(getX), + padding: bandPadding, + }), + [bounds, getX, series, bandPadding] + ); + + /** + * Clip bar width to minimum of 1px otherwise the shape disappears on + * mobile screens. + */ + const barWidth = Math.max(xScale.bandwidth(), 1); + const zeroPosition = getY0({ __value_a: 0, __value_b: 0, __date_unix: 0 }); + + const outOfBoundsItems: SeriesDoubleValue[] = []; + const items: SeriesDoubleValue[] = []; + nonNullSeries.forEach((x) => { + const outOfBounds = undefined !== seriesMax && undefined !== x.__value_a && x.__value_a > seriesMax; + outOfBounds ? outOfBoundsItems.push(x) : items.push(x); + }); + + return ( + <> + {outOfBoundsItems.length && ( + <> + {outOfBoundsItems.map((item, index) => { + const value = { __value: seriesMax, __date_unix: item.__date_unix }; + const x = getX(item) - barWidth / 2; + const y = Math.min(zeroPosition, getY0(value)); + const barHeight = Math.abs(getY0(value) - getY1(value)); + + return ( + + {/* magic-number-alert at the next line the component receives a number as a height and width. + Those are related to the visX library and connot be changed to string/pixel values */} + + + + ); + })} + + )} + + {barWidth > 1 ? ( + <> + {items.map((item, index) => { + const x = getX(item) - barWidth / 2; + const y = Math.min(zeroPosition, getY1(item)); + const barHeight = Math.abs(getY0(item) - getY1(item)); + + return ; + })} + + ) : ( + <> + undefined !== seriesMax && undefined !== x.__value_a && x.__value_a < seriesMax)} + color={color} + fillOpacity={fillOpacity} + strokeWidth={0} + curve="step" + getX={getX} + getY={getY1} + yScale={yScale} + id={id} + /> + + )} + + ); +} + +interface BarTrendIconProps { + color: string; + fillOpacity?: number; + width?: number; + height?: number; +} + +export function StackedBarTrendIcon({ color, fillOpacity = DEFAULT_FILL_OPACITY, width = 15, height = 15 }: BarTrendIconProps) { + const maskId = useUniqueId(); + + return ( + + + + + + + + + ); +} diff --git a/packages/app/src/components/time-series-chart/logic/hover-state.ts b/packages/app/src/components/time-series-chart/logic/hover-state.ts index 67d449a918..09e46dd39c 100644 --- a/packages/app/src/components/time-series-chart/logic/hover-state.ts +++ b/packages/app/src/components/time-series-chart/logic/hover-state.ts @@ -1,35 +1,14 @@ -import { - assert, - endOfDayInSeconds, - isDateSpanValue, - isDateValue, - startOfDayInSeconds, - TimestampedValue, -} from '@corona-dashboard/common'; +import { assert, endOfDayInSeconds, isDateSpanValue, isDateValue, startOfDayInSeconds, TimestampedValue } from '@corona-dashboard/common'; import { localPoint } from '@visx/event'; import { Point } from '@visx/point'; import { bisectCenter } from 'd3-array'; import { ScaleLinear } from 'd3-scale'; import { isEmpty, pick, throttle } from 'lodash'; -import { - Dispatch, - SetStateAction, - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; +import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { isDefined, isPresent } from 'ts-is-present'; import { TimelineEventConfig } from '../components/timeline'; import { Padding, TimespanAnnotationConfig } from './common'; -import { - isVisible, - SeriesConfig, - SeriesDoubleValue, - SeriesList, - SeriesSingleValue, -} from './series'; +import { isVisible, SeriesConfig, SeriesDoubleValue, SeriesList, SeriesSingleValue } from './series'; import { findSplitPointForValue } from './split'; import { useKeyboardNavigation } from './use-keyboard-navigation'; @@ -60,6 +39,7 @@ interface UseHoverStateArgs { interface HoverState { valuesIndex: number; barPoints: HoveredPoint[]; + stackedBarPoints: HoveredPoint[]; linePoints: HoveredPoint[]; rangePoints: HoveredPoint[]; nearestPoint: HoveredPoint; @@ -84,11 +64,7 @@ export function useHoverState({ }: UseHoverStateArgs) { const [point, setPoint] = useState(); const [valuesIndex, setValuesIndex] = useState(0); - const keyboard = useKeyboardNavigation( - setValuesIndex, - values.length, - setIsTabInteractive - ); + const keyboard = useKeyboardNavigation(setValuesIndex, values.length, setIsTabInteractive); useEffect(() => { isTabInteractive ? keyboard.enable() : keyboard.disable(); @@ -111,9 +87,7 @@ export function useHoverState({ ); const valuesWithInteractiveProperties = useMemo(() => { - return values.filter((x) => - hasSomeFilledProperties(pick(x, interactiveMetricProperties)) - ); + return values.filter((x) => hasSomeFilledProperties(pick(x, interactiveMetricProperties))); }, [values, interactiveMetricProperties]); const interactiveValuesDateUnix = useMemo( @@ -173,23 +147,13 @@ export function useHoverState({ const date_unix = xScale.invert(xPosition); - const index = bisectCenter( - interactiveValuesDateUnix, - date_unix, - 0, - interactiveValuesDateUnix.length - ); + const index = bisectCenter(interactiveValuesDateUnix, date_unix, 0, interactiveValuesDateUnix.length); const timestamp = interactiveValuesDateUnix[index]; - const indexInAllValues = allValuesDateUnix.findIndex( - (x) => x === timestamp - ); + const indexInAllValues = allValuesDateUnix.findIndex((x) => x === timestamp); - assert( - indexInAllValues !== -1, - `[${bisect.name}] Failed to find the values index for interactive value timestamp ${timestamp}` - ); + assert(indexInAllValues !== -1, `[${bisect.name}] Failed to find the values index for interactive value timestamp ${timestamp}`); return indexInAllValues; }, @@ -266,9 +230,7 @@ export function useHoverState({ .filter(isVisible) .filter((x) => !x.nonInteractive) .map((config, index) => { - const seriesValue = seriesList[index][valuesIndex] as - | SeriesSingleValue - | undefined; + const seriesValue = seriesList[index][valuesIndex] as SeriesSingleValue | undefined; if (!isPresent(seriesValue)) { return; @@ -307,13 +269,47 @@ export function useHoverState({ }) .filter(isDefined); + const stackedBarPoints: HoveredPoint[] = seriesConfig + .filter(isVisible) + .filter((x) => !x.nonInteractive) + .map((config, index) => { + const seriesValue = seriesList[index][valuesIndex] as SeriesDoubleValue | undefined; + + if (!isPresent(seriesValue)) { + return; + } + + const xValue = seriesValue.__date_unix; + const yValueA = seriesValue.__value_a; + const yValueB = seriesValue.__value_b; + + /** + * Filter series without Y value on the current valuesIndex + */ + if (!isPresent(yValueA) || !isPresent(yValueB)) { + return; + } + + switch (config.type) { + case 'stacked-bar': + return { + seriesValue, + x: xScale(xValue), + y: isPresent(yValueB) ? yScale(yValueB) : 0, + color: config.color, + metricProperty: config.metricProperty, + seriesConfigIndex: index, + }; + } + }) + .filter(isDefined) + .filter((x) => isPresent(x.seriesValue.__value_a) && isPresent(x.seriesValue.__value_b)); + const linePoints: HoveredPoint[] = seriesConfig .filter(isVisible) .filter((x) => !x.nonInteractive) .map((config, index) => { - const seriesValue = seriesList[index][valuesIndex] as - | SeriesSingleValue - | undefined; + const seriesValue = seriesList[index][valuesIndex] as SeriesSingleValue | undefined; if (!isPresent(seriesValue)) { return; @@ -370,9 +366,7 @@ export function useHoverState({ .filter(isVisible) .filter((x) => !x.nonInteractive) .flatMap((config, index) => { - const seriesValue = seriesList[index][valuesIndex] as - | SeriesDoubleValue - | undefined; + const seriesValue = seriesList[index][valuesIndex] as SeriesDoubleValue | undefined; if (!isPresent(seriesValue)) { return; @@ -385,10 +379,7 @@ export function useHoverState({ /** * Filter series without Y value on the current valuesIndex */ - if ( - (!isPresent(yValueA) || !isPresent(yValueB)) && - config.type !== 'gapped-stacked-area' - ) { + if ((!isPresent(yValueA) || !isPresent(yValueB)) && config.type !== 'gapped-stacked-area') { return; } @@ -427,20 +418,16 @@ export function useHoverState({ } }) .filter(isDefined) - .filter( - (x) => - isPresent(x.seriesValue.__value_a) && - isPresent(x.seriesValue.__value_b) - ); + .filter((x) => isPresent(x.seriesValue.__value_a) && isPresent(x.seriesValue.__value_b)); /** * For nearest point calculation we only need to look at the y component of * the mouse, since all series originate from the same original value and * are thus aligned with the same timestamp. */ - const nearestPoint = [...linePoints, ...rangePoints, ...barPoints].sort( - (a, b) => Math.abs(a.y - pointY) - Math.abs(b.y - pointY) - )[0] as HoveredPoint | undefined; + const nearestPoint = [...linePoints, ...rangePoints, ...barPoints, ...stackedBarPoints].sort((a, b) => Math.abs(a.y - pointY) - Math.abs(b.y - pointY))[0] as + | HoveredPoint + | undefined; /** * Empty hoverstate when there's no nearest point detected @@ -449,60 +436,31 @@ export function useHoverState({ return; } - const timespanAnnotationIndex = timespanAnnotations - ? findActiveTimespanAnnotationIndex( - values[valuesIndex], - timespanAnnotations - ) - : undefined; + const timespanAnnotationIndex = timespanAnnotations ? findActiveTimespanAnnotationIndex(values[valuesIndex], timespanAnnotations) : undefined; - const timelineEventIndex = timelineEvents - ? findActiveTimelineEventIndex(values[valuesIndex], timelineEvents) - : undefined; + const timelineEventIndex = timelineEvents ? findActiveTimelineEventIndex(values[valuesIndex], timelineEvents) : undefined; const hoverState: HoverState = { valuesIndex, barPoints, - linePoints: markNearestPointOnly - ? linePoints.filter((x) => x === nearestPoint) - : linePoints, - rangePoints: markNearestPointOnly - ? rangePoints.filter((x) => x === nearestPoint) - : rangePoints, + stackedBarPoints, + linePoints: markNearestPointOnly ? linePoints.filter((x) => x === nearestPoint) : linePoints, + rangePoints: markNearestPointOnly ? rangePoints.filter((x) => x === nearestPoint) : rangePoints, nearestPoint, timespanAnnotationIndex, timelineEventIndex, }; return hoverState; - }, [ - point, - isTabInteractive, - seriesConfig, - timespanAnnotations, - timelineEvents, - values, - valuesIndex, - markNearestPointOnly, - seriesList, - xScale, - yScale, - ]); + }, [point, isTabInteractive, seriesConfig, timespanAnnotations, timelineEvents, values, valuesIndex, markNearestPointOnly, seriesList, xScale, yScale]); return [hoverState, { handleHover }] as const; } -function findActiveTimespanAnnotationIndex( - hoveredValue: TimestampedValue, - timespanAnnotations: TimespanAnnotationConfig[] -) { - const valueSpanStart = isDateValue(hoveredValue) - ? hoveredValue.date_unix - : hoveredValue.date_start_unix; +function findActiveTimespanAnnotationIndex(hoveredValue: TimestampedValue, timespanAnnotations: TimespanAnnotationConfig[]) { + const valueSpanStart = isDateValue(hoveredValue) ? hoveredValue.date_unix : hoveredValue.date_start_unix; - const valueSpanEnd = isDateValue(hoveredValue) - ? hoveredValue.date_unix - : hoveredValue.date_end_unix; + const valueSpanEnd = isDateValue(hoveredValue) ? hoveredValue.date_unix : hoveredValue.date_end_unix; /** * Loop over the annotations and see if the hovered value falls within its @@ -519,21 +477,10 @@ function findActiveTimespanAnnotationIndex( } } -function findActiveTimelineEventIndex( - hoveredValue: TimestampedValue, - timelineEvents: TimelineEventConfig[] -) { - const valueSpanStartOfDay = startOfDayInSeconds( - isDateValue(hoveredValue) - ? hoveredValue.date_unix - : hoveredValue.date_start_unix - ); +function findActiveTimelineEventIndex(hoveredValue: TimestampedValue, timelineEvents: TimelineEventConfig[]) { + const valueSpanStartOfDay = startOfDayInSeconds(isDateValue(hoveredValue) ? hoveredValue.date_unix : hoveredValue.date_start_unix); - const valueSpanEndOfDay = endOfDayInSeconds( - isDateValue(hoveredValue) - ? hoveredValue.date_unix - : hoveredValue.date_end_unix - ); + const valueSpanEndOfDay = endOfDayInSeconds(isDateValue(hoveredValue) ? hoveredValue.date_unix : hoveredValue.date_end_unix); /** * Loop over the timeline events and see if the hovered value falls within its diff --git a/packages/app/src/components/time-series-chart/logic/series.ts b/packages/app/src/components/time-series-chart/logic/series.ts index 129a261b0e..284021bbee 100644 --- a/packages/app/src/components/time-series-chart/logic/series.ts +++ b/packages/app/src/components/time-series-chart/logic/series.ts @@ -13,6 +13,7 @@ type SeriesConfigSingle = | RangeSeriesDefinition | AreaSeriesDefinition | StackedAreaSeriesDefinition + | StackedBarSeriesDefinition | BarSeriesDefinition | BarOutOfBoundsSeriesDefinition | SplitBarSeriesDefinition @@ -177,6 +178,15 @@ export interface GappedStackedAreaSeriesDefinition e mixBlendMode?: Property.MixBlendMode; } +export interface StackedBarSeriesDefinition extends SeriesCommonDefinition { + type: 'stacked-bar'; + metricProperty: keyof T; + label: string; + shortLabel?: string; + color: string; + fillOpacity?: number; +} + /** * Adding the split series definition here even though it might not end up as * part of this chart. For starters this makes it easier because then we can @@ -353,6 +363,8 @@ function getSeriesList(values: T[], seriesConfig: Se ? getGappedStackedAreaSeriesData(values, config.metricProperty, seriesConfig, dataOptions) : config.type === 'range' ? getRangeSeriesData(values, config.metricPropertyLow, config.metricPropertyHigh) + : config.type === 'stacked-bar' + ? getStackedBarSeriesData(values, config.metricProperty, seriesConfig) : /** * Cutting values based on annotation is only supported for single line series */ @@ -442,6 +454,40 @@ function getStackedAreaSeriesData(values: T[], metri }); } +function getStackedBarSeriesData(values: T[], metricProperty: keyof T, seriesConfig: SeriesConfig) { + /** + * Stacked area series are rendered from top to bottom. The sum of a Y-value + * of all series below the current series equals the low value of a current + * series's Y-value. + */ + const stackedAreaDefinitions = seriesConfig.filter(hasValueAtKey('type', 'stacked-bar' as const)); + + const seriesBelowCurrentSeries = getSeriesBelowCurrentSeries(stackedAreaDefinitions, metricProperty); + + const seriesHigh = getSeriesData(values, metricProperty); + const seriesLow = getSeriesData(values, metricProperty); + + seriesLow.forEach((seriesSingleValue, index) => { + /** + * The series are rendered from top to bottom. To get the low value of the + * current series, we will sum up all values of the + * `seriesBelowCurrentSeries`. + */ + seriesSingleValue.__value = sumSeriesValues(seriesBelowCurrentSeries, values, index); + }); + + return seriesLow.map((low, index) => { + const valueLow = low.__value ?? 0; + const valueHigh = valueLow + (seriesHigh[index].__value ?? 0); + + return { + __date_unix: low.__date_unix, + __value_a: valueLow, + __value_b: valueHigh, + }; + }); +} + function getSeriesBelowCurrentSeries(definitions: { metricProperty: keyof T }[], metricProperty: keyof T) { return definitions.slice(definitions.findIndex((x) => x.metricProperty === metricProperty) + 1); } diff --git a/packages/app/src/domain/variants/logic/use-bar-config.ts b/packages/app/src/domain/variants/logic/use-bar-config.ts index edef57756e..966d672936 100644 --- a/packages/app/src/domain/variants/logic/use-bar-config.ts +++ b/packages/app/src/domain/variants/logic/use-bar-config.ts @@ -1,7 +1,8 @@ -import { ColorMatch, StackedBarConfig, VariantChartValue, VariantDynamicLabels, VariantsOverTimeGraphText } from '~/domain/variants/data-selection/types'; +import { ColorMatch, VariantChartValue, 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'; +import { StackedBarSeriesDefinition } from '~/components/time-series-chart/logic'; const extractVariantNamesFromValues = (values: VariantChartValue[]) => { return values @@ -42,7 +43,7 @@ export const useBarConfig = ( .filter((keyName) => activeVariantsInTimeframeNames.includes(keyName)) .reverse(); - const barChartConfig: StackedBarConfig[] = []; + const barChartConfig: StackedBarSeriesDefinition[] = []; listOfVariantCodes.forEach((variantKey) => { const variantCodeName = variantKey.split('_').slice(0, -1).join('_'); @@ -55,13 +56,15 @@ export const useBarConfig = ( if (variantDynamicLabel) { const barChartConfigEntry = { + type: 'stacked-bar', metricProperty: variantMetricPropertyName, color: color, label: variantDynamicLabel, + fillOpacity: 1, shape: 'gapped-area', }; - barChartConfig.push(barChartConfigEntry as StackedBarConfig); + barChartConfig.push(barChartConfigEntry as StackedBarSeriesDefinition); } }); 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 index 87abe2c2a3..85116b018c 100644 --- a/packages/app/src/domain/variants/variants-stacked-bar-chart-tile.tsx +++ b/packages/app/src/domain/variants/variants-stacked-bar-chart-tile.tsx @@ -1,4 +1,4 @@ -import { ChartTile, MetadataProps } from '~/components'; +import { ChartTile, MetadataProps, TimeSeriesChart } from '~/components'; import { Spacer } from '~/components/base'; import { TimeframeOption, TimeframeOptionsList } from '@corona-dashboard/common'; import { useState } from 'react'; @@ -11,7 +11,6 @@ import { space } from '~/style/theme'; import { useCurrentDate } from '~/utils/current-date-context'; import { reorderAndFilter } from '~/domain/variants/logic/reorder-and-filter'; import { useIntl } from '~/intl'; -import { StackedBarTooltipData, StackedChart } from '~/components/stacked-chart'; interface VariantsStackedBarChartTileProps { title: string; @@ -62,15 +61,15 @@ export const VariantsStackedBarChartTile = ({ title, description, tooltipLabels, > - (data, interactiveLegendOptions)} hasTwoColumns={hasTwoColumns} />} + formatTooltip={(data) => (data, interactiveLegendOptions)} hasTwoColumns={hasTwoColumns} />} /> ); From 3e983eb45a3ddf41a4c1945559da621cd3594ce0 Mon Sep 17 00:00:00 2001 From: BE <137172332+VWSCoronaDashboard29@users.noreply.github.com> Date: Tue, 31 Oct 2023 11:32:52 +0100 Subject: [PATCH 18/19] fix(COR-1811): correct y-axis stacked chart (#4924) Co-authored-by: VWSCoronaDashboard29 --- packages/app/src/components/stacked-chart/stacked-chart.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/app/src/components/stacked-chart/stacked-chart.tsx b/packages/app/src/components/stacked-chart/stacked-chart.tsx index fa147a30e7..cbb52b4407 100644 --- a/packages/app/src/components/stacked-chart/stacked-chart.tsx +++ b/packages/app/src/components/stacked-chart/stacked-chart.tsx @@ -419,7 +419,7 @@ export function StackedChart(props: StackedChartProp * negative height is not allowed. */ y={bar.y + (isTinyScreen ? 1 : 2)} - height={Math.max(0, bar.height)} + height={Math.max(0, bar.height - (isTinyScreen ? 1 : 2))} width={bar.width} fill={fillColor} onMouseLeave={handleHoverWithBar} @@ -436,7 +436,7 @@ export function StackedChart(props: StackedChartProp * negative height is not allowed. */ y={bar.y + (isTinyScreen ? 1 : 2)} - height={Math.max(0, bar.height)} + height={Math.max(0, bar.height - (isTinyScreen ? 1 : 2))} width={bar.width} fill={breakpoints.lg ? 'url(#pattern-hatched)' : 'url(#pattern-hatched-small)'} onMouseLeave={handleHoverWithBar} From 10f44759bb047a265e3e60f44a91b309cc67ac9d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Nov 2023 11:25:51 +0100 Subject: [PATCH 19/19] chore(deps): bump browserify-sign from 4.2.1 to 4.2.2 (#4919) Bumps [browserify-sign](https://github.com/crypto-browserify/browserify-sign) from 4.2.1 to 4.2.2. - [Changelog](https://github.com/browserify/browserify-sign/blob/main/CHANGELOG.md) - [Commits](https://github.com/crypto-browserify/browserify-sign/compare/v4.2.1...v4.2.2) --- updated-dependencies: - dependency-name: browserify-sign dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 46 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/yarn.lock b/yarn.lock index 60e40a87ff..99c9d593cb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13227,13 +13227,20 @@ __metadata: languageName: node linkType: hard -"bn.js@npm:^5.0.0, bn.js@npm:^5.1.1": +"bn.js@npm:^5.0.0": version: 5.2.0 resolution: "bn.js@npm:5.2.0" checksum: 6117170393200f68b35a061ecbf55d01dd989302e7b3c798a3012354fa638d124f0b2f79e63f77be5556be80322a09c40339eda6413ba7468524c0b6d4b4cb7a languageName: node linkType: hard +"bn.js@npm:^5.2.1": + version: 5.2.1 + resolution: "bn.js@npm:5.2.1" + checksum: 3dd8c8d38055fedfa95c1d5fc3c99f8dd547b36287b37768db0abab3c239711f88ff58d18d155dd8ad902b0b0cee973747b7ae20ea12a09473272b0201c9edd3 + languageName: node + linkType: hard + "body-parser@npm:1.19.0": version: 1.19.0 resolution: "body-parser@npm:1.19.0" @@ -13417,7 +13424,7 @@ __metadata: languageName: node linkType: hard -"browserify-rsa@npm:^4.0.0, browserify-rsa@npm:^4.0.1": +"browserify-rsa@npm:^4.0.0, browserify-rsa@npm:^4.1.0": version: 4.1.0 resolution: "browserify-rsa@npm:4.1.0" dependencies: @@ -13428,19 +13435,19 @@ __metadata: linkType: hard "browserify-sign@npm:^4.0.0": - version: 4.2.1 - resolution: "browserify-sign@npm:4.2.1" + version: 4.2.2 + resolution: "browserify-sign@npm:4.2.2" dependencies: - bn.js: ^5.1.1 - browserify-rsa: ^4.0.1 + bn.js: ^5.2.1 + browserify-rsa: ^4.1.0 create-hash: ^1.2.0 create-hmac: ^1.1.7 - elliptic: ^6.5.3 + elliptic: ^6.5.4 inherits: ^2.0.4 - parse-asn1: ^5.1.5 - readable-stream: ^3.6.0 - safe-buffer: ^5.2.0 - checksum: 0221f190e3f5b2d40183fa51621be7e838d9caa329fe1ba773406b7637855f37b30f5d83e52ff8f244ed12ffe6278dd9983638609ed88c841ce547e603855707 + parse-asn1: ^5.1.6 + readable-stream: ^3.6.2 + safe-buffer: ^5.2.1 + checksum: b622730c0fc183328c3a1c9fdaaaa5118821ed6822b266fa6b0375db7e20061ebec87301d61931d79b9da9a96ada1cab317fce3c68f233e5e93ed02dbb35544c languageName: node linkType: hard @@ -16471,7 +16478,7 @@ __metadata: languageName: node linkType: hard -"elliptic@npm:^6.5.3": +"elliptic@npm:^6.5.3, elliptic@npm:^6.5.4": version: 6.5.4 resolution: "elliptic@npm:6.5.4" dependencies: @@ -24646,7 +24653,7 @@ __metadata: languageName: node linkType: hard -"parse-asn1@npm:^5.0.0, parse-asn1@npm:^5.1.5": +"parse-asn1@npm:^5.0.0, parse-asn1@npm:^5.1.6": version: 5.1.6 resolution: "parse-asn1@npm:5.1.6" dependencies: @@ -27970,6 +27977,17 @@ __metadata: languageName: node linkType: hard +"readable-stream@npm:^3.6.2": + version: 3.6.2 + resolution: "readable-stream@npm:3.6.2" + dependencies: + inherits: ^2.0.3 + string_decoder: ^1.1.1 + util-deprecate: ^1.0.1 + checksum: bdcbe6c22e846b6af075e32cf8f4751c2576238c5043169a1c221c92ee2878458a816a4ea33f4c67623c0b6827c8a400409bfb3cf0bf3381392d0b1dfb52ac8d + languageName: node + linkType: hard + "readdir-glob@npm:^1.0.0": version: 1.1.1 resolution: "readdir-glob@npm:1.1.1" @@ -28905,7 +28923,7 @@ __metadata: languageName: node linkType: hard -"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.0, safe-buffer@npm:~5.2.0": +"safe-buffer@npm:5.2.1, safe-buffer@npm:^5.0.1, safe-buffer@npm:^5.1.0, safe-buffer@npm:^5.1.1, safe-buffer@npm:^5.1.2, safe-buffer@npm:^5.2.0, safe-buffer@npm:^5.2.1, safe-buffer@npm:~5.2.0": version: 5.2.1 resolution: "safe-buffer@npm:5.2.1" checksum: b99c4b41fdd67a6aaf280fcd05e9ffb0813654894223afb78a31f14a19ad220bba8aba1cb14eddce1fcfb037155fe6de4e861784eb434f7d11ed58d1e70dd491