diff --git a/packages/app/src/domain/variants/static-props/get-variant-order-colors.ts b/packages/app/src/domain/variants/static-props/get-variant-order-colors.ts index f37edb5821..d899e624a9 100644 --- a/packages/app/src/domain/variants/static-props/get-variant-order-colors.ts +++ b/packages/app/src/domain/variants/static-props/get-variant-order-colors.ts @@ -1,6 +1,6 @@ import { NlVariants, colors } from '@corona-dashboard/common'; import { isDefined } from 'ts-is-present'; -import { VariantCode } from './' +import { VariantCode } from './'; export type ColorMatch = { variant: VariantCode; @@ -12,29 +12,23 @@ const getColorForVariant = (variantCode: VariantCode, index: number): string => if (variantCode === 'other_graph') return colors.gray5; return colors.variants.colorList[index]; -} +}; -export function getVariantOrderColors( - variants: NlVariants | undefined -): ColorMatch[] { +export const getVariantOrderColors = (variants: NlVariants | undefined): ColorMatch[] => { if (!isDefined(variants) || !isDefined(variants.values)) { return []; } const colorOrder = variants.values - .filter( - (variant) => - variant.last_value.is_variant_of_concern || - variant.last_value.has_historical_significance - ) - .sort((a, b) => a.last_value.order - b.last_value.order) + .filter((variant) => variant.last_value.is_variant_of_concern || variant.last_value.has_historical_significance) + .sort((a, b) => (a.variant_code.includes('other') || b.variant_code.includes('other') ? -1 : a.last_value.order - b.last_value.order)) .map((variant, index) => { - const variantColor = getColorForVariant(variant.variant_code, index) + const variantColor = getColorForVariant(variant.variant_code, index); return { variant: variant.variant_code, - color: variantColor + color: variantColor, }; - }); + }); return colorOrder; -} +}; 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/variants-stacked-area-tile.tsx index 6eff3614c0..b95a3f1680 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/variants-stacked-area-tile.tsx @@ -1,11 +1,5 @@ -import { - colors, - TimeframeOption, - TimeframeOptionsList, -} from '@corona-dashboard/common'; -import css from '@styled-system/css'; -import { ReactNode, useMemo, useState } from 'react'; -import styled from 'styled-components'; +import { colors, 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'; @@ -20,95 +14,34 @@ import { SiteText } from '~/locale'; import { useList } from '~/utils/use-list'; import { ColorMatch, VariantCode } from '~/domain/variants/static-props'; import { useUnreliableDataAnnotations } from './logic/use-unreliable-data-annotations'; +import { space } from '~/style/theme'; type VariantsStackedAreaTileText = { variantCodes: SiteText['common']['variant_codes']; } & SiteText['pages']['variants_page']['nl']['varianten_over_tijd_grafiek']; -type VariantsStackedAreaTileProps = { - text: VariantsStackedAreaTileText; - values?: VariantChartValue[] | null; - variantColors: ColorMatch[]; - metadata: MetadataProps; - children?: ReactNode; - noDataMessage?: ReactNode; -}; - -export function VariantsStackedAreaTile({ - values, - variantColors, - metadata, - children = null, - noDataMessage = '', - text, -}: VariantsStackedAreaTileProps) { - if (!isPresent(values)) { - return ( - - {children} - {noDataMessage} - - ); - } - return ( - - {children} - - ); -} - const alwaysEnabled: (keyof VariantChartValue)[] = []; -type VariantStackedAreaTileWithDataProps = { +interface VariantsStackedAreaTileProps { text: VariantsStackedAreaTileText; values: VariantChartValue[]; metadata: MetadataProps; variantColors: ColorMatch[]; - children?: ReactNode; -}; +} -function VariantStackedAreaTileWithData({ - text, - values, - variantColors, - metadata, - children = null, -}: VariantStackedAreaTileWithDataProps) { - const [variantTimeframe, setVariantTimeframe] = useState( - TimeframeOption.ALL - ); +export const VariantsStackedAreaTile = ({ text, values, variantColors, metadata }: VariantsStackedAreaTileProps) => { + const [variantTimeframe, setVariantTimeframe] = useState(TimeframeOption.THREE_MONTHS); - const { list, toggle, clear } = - useList(alwaysEnabled); + const { list, toggle, clear } = useList(alwaysEnabled); - const [seriesConfig, otherConfig, selectOptions] = useSeriesConfig( - text, - values, - variantColors - ); + const [seriesConfig, otherConfig, selectOptions] = useSeriesConfig(text, values, variantColors); - const filteredConfig = useFilteredSeriesConfig( - seriesConfig, - otherConfig, - list - ); + const filteredConfig = useFilteredSeriesConfig(seriesConfig, otherConfig, list); /* Static legend contains only the inaccurate item */ const staticLegendItems: LegendItem[] = []; - const timespanAnnotations = useUnreliableDataAnnotations( - values, - text.lagere_betrouwbaarheid - ); + const timespanAnnotations = useUnreliableDataAnnotations(values, text.lagere_betrouwbaarheid); if (timespanAnnotations.length) { staticLegendItems.push({ @@ -124,149 +57,104 @@ function VariantStackedAreaTileWithData({ description={text.toelichting} metadata={metadata} timeframeOptions={TimeframeOptionsList} - timeframeInitialValue={TimeframeOption.SIX_MONTHS} + timeframeInitialValue={TimeframeOption.THREE_MONTHS} onSelectTimeframe={setVariantTimeframe} > - <> - {children} - {children && } - - - { - /** - * 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: [ - ...context.config.filter( - (value) => - !hasMetricProperty(value) || - filteredValues[value.metricProperty] || - hasSelectedMetrics - ), - context.config.find( - (value) => - hasMetricProperty(value) && - value.metricProperty === 'other_graph_percentage' - ), - ].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 ( - - ); - }} - numGridLines={0} - tickValues={[0, 25, 50, 75, 100]} - /> - - + + + { + /** + * 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: [ + ...context.config.filter((value) => !hasMetricProperty(value) || filteredValues[value.metricProperty] || hasSelectedMetrics), + context.config.find((value) => hasMetricProperty(value) && value.metricProperty === 'other_graph_percentage'), + ].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 ; + }} + numGridLines={0} + tickValues={[0, 25, 50, 75, 100]} + /> + ); -} +}; -function hasMetricProperty(config: any): config is { metricProperty: string } { +const hasMetricProperty = (config: any): config is { metricProperty: string } => { return 'metricProperty' in config; -} +}; -function useFilteredSeriesConfig( +const useFilteredSeriesConfig = ( seriesConfig: GappedAreaSeriesDefinition[], otherConfig: GappedAreaSeriesDefinition, compareList: (keyof VariantChartValue)[] -) { +) => { return useMemo(() => { return [otherConfig, ...seriesConfig].filter( - (item) => - item.metricProperty !== 'other_graph_percentage' && - (compareList.includes(item.metricProperty) || - compareList.length === alwaysEnabled.length) + (item) => item.metricProperty !== 'other_graph_percentage' && (compareList.includes(item.metricProperty) || compareList.length === alwaysEnabled.length) ); }, [seriesConfig, otherConfig, compareList]); -} +}; -function useSeriesConfig( - text: VariantsStackedAreaTileText, - values: VariantChartValue[], - variantColors: ColorMatch[] -) { +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') && x !== 'other_graph_percentage' - ) + .filter((x) => x.endsWith('_percentage') && x !== 'other_graph_percentage') .reverse(); // Reverse to be in an alphabetical order /* Enrich config with dynamic data / locale */ - const seriesConfig: GappedAreaSeriesDefinition[] = - baseVariantsFiltered.map((variantKey) => { - const variantCodeFragments = variantKey.split('_'); - variantCodeFragments.pop(); - const variantCode = variantCodeFragments.join('_') as VariantCode; - - const color = - variantColors.find( - (variantColors) => variantColors.variant === variantCode - )?.color || colors.gray5; - - return { - type: 'gapped-area', - metricProperty: variantKey as keyof VariantChartValue, - color, - label: text.variantCodes[variantCode], - shape: 'gapped-area', - strokeWidth: 2, - fillOpacity: 0.2, - mixBlendMode: 'multiply', - }; - }); + const seriesConfig: GappedAreaSeriesDefinition[] = baseVariantsFiltered.map((variantKey) => { + const variantCodeFragments = variantKey.split('_'); + variantCodeFragments.pop(); + const variantCode = variantCodeFragments.join('_') as VariantCode; + + const color = variantColors.find((variantColors) => variantColors.variant === variantCode)?.color || colors.gray5; + + return { + type: 'gapped-area', + metricProperty: variantKey as keyof VariantChartValue, + color, + label: text.variantCodes[variantCode], + shape: 'gapped-area', + strokeWidth: 2, + fillOpacity: 0.2, + mixBlendMode: 'multiply', + }; + }); const otherConfig = { type: 'gapped-area', @@ -282,21 +170,5 @@ function useSeriesConfig( const selectOptions = [...seriesConfig]; return [seriesConfig, otherConfig, selectOptions] as const; - }, [ - values, - text.tooltip_labels.other_percentage, - text.variantCodes, - variantColors, - ]); -} - -const NoDataBox = styled.div( - css({ - width: '100%', - display: 'flex', - height: '8em', - color: 'gray5', - justifyContent: 'center', - alignItems: 'center', - }) -); + }, [values, text.tooltip_labels.other_percentage, text.variantCodes, variantColors]); +}; diff --git a/packages/app/src/pages/landelijk/varianten.tsx b/packages/app/src/pages/landelijk/varianten.tsx index 498544b8fd..8ebced13cd 100644 --- a/packages/app/src/pages/landelijk/varianten.tsx +++ b/packages/app/src/pages/landelijk/varianten.tsx @@ -4,30 +4,14 @@ 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 { 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 { useIntl } from '~/intl'; import { Languages, SiteText } from '~/locale'; -import { - getArticleParts, - getLinkParts, - getPagePartsQuery, -} from '~/queries/get-page-parts-query'; -import { - createGetStaticProps, - StaticProps, -} from '~/static-props/create-get-static-props'; -import { - createGetContent, - getLastGeneratedDate, - selectNlData, - getLokalizeTexts, -} from '~/static-props/get-data'; +import { getArticleParts, getLinkParts, getPagePartsQuery } from '~/queries/get-page-parts-query'; +import { createGetStaticProps, StaticProps } from '~/static-props/create-get-static-props'; +import { createGetContent, getLastGeneratedDate, selectNlData, getLokalizeTexts } from '~/static-props/get-data'; import { ArticleParts, LinkParts, PagePartQueryResult } from '~/types/cms'; import { getLastInsertionDateOfPage } from '~/utils/get-last-insertion-date-of-page'; import { useDynamicLokalizeTexts } from '~/utils/cms/use-dynamic-lokalize-texts'; @@ -43,8 +27,7 @@ const selectLokalizeTexts = (siteText: SiteText) => ({ type LokalizeTexts = ReturnType; export const getStaticProps = createGetStaticProps( - ({ locale }: { locale: keyof Languages }) => - getLokalizeTexts(selectLokalizeTexts, locale), + ({ locale }: { locale: keyof Languages }) => getLokalizeTexts(selectLokalizeTexts, locale), selectNlData('variants', 'named_difference'), getLastGeneratedDate, () => { @@ -57,19 +40,13 @@ export const getStaticProps = createGetStaticProps( const variantColors = getVariantOrderColors(variants); return { - ...getVariantTableData( - variants, - data.selectedNlData.named_difference, - variantColors - ), + ...getVariantTableData(variants, data.selectedNlData.named_difference, variantColors), ...getVariantChartData(variants), variantColors, }; }, async (context: GetStaticPropsContext) => { - const { content } = await createGetContent< - PagePartQueryResult - >(() => getPagePartsQuery('variants_page'))(context); + const { content } = await createGetContent>(() => getPagePartsQuery('variants_page'))(context); return { content: { @@ -80,23 +57,11 @@ export const getStaticProps = createGetStaticProps( } ); -export default function CovidVariantenPage( - props: StaticProps -) { - const { - pageText, - selectedNlData: data, - lastGenerated, - content, - variantTable, - variantChart, - variantColors, - dates, - } = props; +export default function CovidVariantenPage(props: StaticProps) { + const { pageText, selectedNlData: data, lastGenerated, content, variantTable, variantChart, variantColors, dates } = props; const { commonTexts } = useIntl(); - const { metadataTexts, textNl, textShared } = - useDynamicLokalizeTexts(pageText, selectLokalizeTexts); + const { metadataTexts, textNl, textShared } = useDynamicLokalizeTexts(pageText, selectLokalizeTexts); const metadata = { ...metadataTexts, @@ -111,9 +76,7 @@ export default function CovidVariantenPage(