diff --git a/packages/app/src/components/interactive-legend.tsx b/packages/app/src/components/interactive-legend.tsx index 1601732cf6..8fa5d3d379 100644 --- a/packages/app/src/components/interactive-legend.tsx +++ b/packages/app/src/components/interactive-legend.tsx @@ -4,13 +4,14 @@ import styled from 'styled-components'; import { isDefined } from 'ts-is-present'; import { BoldText } from '~/components/typography'; import { useIntl } from '~/intl'; +import { space } from '~/style/theme'; import { Box } from './base'; export interface SelectOption { metricProperty: T; label: string; color: string; - shape?: 'line' | 'circle' | 'square' | 'gapped-area'; + shape?: 'line' | 'circle' | 'square' | 'gapped-area' | 'dashed'; legendAriaLabel?: string; } @@ -22,13 +23,7 @@ interface InteractiveLegendProps { onReset?: () => void; } -export function InteractiveLegend({ - helpText, - selectOptions, - selection, - onToggleItem, - onReset, -}: InteractiveLegendProps) { +export function InteractiveLegend({ helpText, selectOptions, selection, onToggleItem, onReset }: InteractiveLegendProps) { const { commonTexts } = useIntl(); const hasSelection = selection.length !== 0; @@ -42,19 +37,17 @@ export function InteractiveLegend({ const isSelected = selection.includes(item.metricProperty); return ( - + {item.label} {item.shape === 'line' && } + {item.shape === 'dashed' && ( + + + + )} {item.shape === 'circle' && } {item.shape === 'square' && } - {item.shape === 'gapped-area' && ( - - )} + {item.shape === 'gapped-area' && } (({ isVisible }) => backgroundColor: 'transparent', cursor: 'pointer', color: 'blue8', - py: '6px', + paddingY: '6px', border: 'none', fontFamily: 'inherit', visibility: isVisible ? 'visible' : 'hidden', @@ -200,26 +191,49 @@ const ResetButton = styled.button<{ isVisible: boolean }>(({ isVisible }) => }) ); -const Line = styled.div<{ color: string }>(({ color }) => +const DashedContainer = styled(Box)` + svg { + display: block; + left: 13px; + position: absolute; + top: 50%; + transform: translateY(-50%); + } +`; + +interface DashedProps { + color: string; +} + +export const Dashed = ({ color }: DashedProps) => { + return ( + + + + ); +}; + +const Shape = styled.div<{ color: string }>((x) => + css({ + display: 'block', + position: 'absolute', + left: 13, + backgroundColor: x.color, + }) +); + +const Line = styled(Shape)( css({ top: '50%', transform: 'translateY(-50%)', width: '15px', height: '3px', borderRadius: '2px', - display: 'block', - position: 'absolute', - left: 13, - backgroundColor: color, }) ); -const Circle = styled.div<{ color: string }>(({ color }) => +const Circle = styled(Shape)( css({ - display: 'block', - position: 'absolute', - left: 13, - backgroundColor: color, top: '50%', transform: 'translateY(-50%)', width: '10px', @@ -228,12 +242,8 @@ const Circle = styled.div<{ color: string }>(({ color }) => }) ); -const Square = styled.div<{ color: string }>(({ color }) => +const Square = styled(Shape)( css({ - display: 'block', - position: 'absolute', - left: 13, - backgroundColor: color, top: '50%', transform: 'translateY(-50%)', width: '11px', @@ -242,11 +252,8 @@ const Square = styled.div<{ color: string }>(({ color }) => }) ); -const GappedArea = styled.div<{ color: string }>(({ color }) => +const GappedArea = styled(Shape)<{ color: string }>(({ color }) => css({ - display: 'block', - position: 'absolute', - left: 13, backgroundColor: `${color}30`, borderTop: `2px solid ${color}`, top: '50%', diff --git a/packages/app/src/components/legend.tsx b/packages/app/src/components/legend.tsx index e79f97103f..fd396ba487 100644 --- a/packages/app/src/components/legend.tsx +++ b/packages/app/src/components/legend.tsx @@ -2,13 +2,9 @@ import { colors } from '@corona-dashboard/common'; import css, { SystemStyleObject } from '@styled-system/css'; import { ReactNode } from 'react'; import styled from 'styled-components'; +import { space } from '~/style/theme'; -type LegendShape = - | 'line' - | 'square' - | 'circle' - | 'dotted-square' - | 'outlined-square'; +type LegendShape = 'line' | 'square' | 'circle' | 'dotted-square' | 'outlined-square'; type LegendLineStyle = 'solid' | 'dashed'; export type LegendItem = { @@ -44,15 +40,9 @@ export function Legend({ items, columns }: LegendProps) { {item.label} {item.shape === 'square' && } - {item.shape === 'outlined-square' && ( - - )} - {item.shape === 'dotted-square' && ( - - )} - {item.shape === 'line' && ( - - )} + {item.shape === 'outlined-square' && } + {item.shape === 'dotted-square' && } + {item.shape === 'line' && } {item.shape === 'circle' && } ); @@ -64,8 +54,8 @@ export function Legend({ items, columns }: LegendProps) { const List = styled.ul<{ columns?: number }>(({ columns }) => css({ listStyle: 'none', - px: 0, - m: 0, + paddingX: 0, + margin: 0, fontSize: 1, color: 'gray7', columns, @@ -75,11 +65,11 @@ const List = styled.ul<{ columns?: number }>(({ columns }) => const Item = styled.li( css({ - my: 1, - mr: 3, + marginY: space[1], + marginRight: space[3], position: 'relative', display: 'inline-block', - pl: '25px', // alignment with shape + paddingLeft: '25px', // alignment with shape }) ); @@ -109,31 +99,14 @@ const Shape = styled.div<{ color: string }>((x) => function DottedSquare({ color }: { color: string }) { return ( - + - - + + - + @@ -170,18 +143,14 @@ const Circle = styled(Shape)( }) ); -const Line = styled.div<{ color: string; lineStyle: LegendLineStyle }>( - ({ color, lineStyle }) => - css({ - display: 'block', - position: 'absolute', - borderTopColor: color as SystemStyleObject, - borderTopStyle: lineStyle, - borderTopWidth: '3px', - top: '10px', - width: '15px', - height: 0, - borderRadius: '2px', - left: 0, - }) +const Line = styled(Shape)<{ color: string; lineStyle: LegendLineStyle }>(({ color, lineStyle }) => + css({ + borderTopColor: color as SystemStyleObject, + borderTopStyle: lineStyle, + borderTopWidth: '3px', + top: '10px', + width: '15px', + height: '0', + borderRadius: '2px', + }) ); diff --git a/packages/app/src/components/time-series-chart/logic/legend.tsx b/packages/app/src/components/time-series-chart/logic/legend.tsx index f05ef2267c..af383ed984 100644 --- a/packages/app/src/components/time-series-chart/logic/legend.tsx +++ b/packages/app/src/components/time-series-chart/logic/legend.tsx @@ -26,6 +26,7 @@ export function useLegendItems( const legendItems = config .filter(isVisible) .filter((configItem) => !configItem?.hideInLegend) + .map((x) => { switch (x.type) { case 'split-area': @@ -111,7 +112,6 @@ export function useLegendItems( * legend when there's at least two items. */ const isLegendRequired = forceLegend || legendItems.length + splitLegendGroups.length > 1; - return { legendItems: isLegendRequired ? legendItems : undefined, splitLegendGroups: splitLegendGroups.length > 0 ? splitLegendGroups : undefined, diff --git a/packages/app/src/domain/hospital/admissions-per-age-group/admissions-per-age-group.tsx b/packages/app/src/domain/hospital/admissions-per-age-group/admissions-per-age-group.tsx index ed46851b94..d5e00ce4cb 100644 --- a/packages/app/src/domain/hospital/admissions-per-age-group/admissions-per-age-group.tsx +++ b/packages/app/src/domain/hospital/admissions-per-age-group/admissions-per-age-group.tsx @@ -1,15 +1,7 @@ -import { - DAY_IN_SECONDS, - NlHospitalNicePerAgeGroupValue, - NlIntensiveCareNicePerAgeGroupValue, - TimeframeOption, -} from '@corona-dashboard/common'; +import { DAY_IN_SECONDS, NlHospitalNicePerAgeGroupValue, NlIntensiveCareNicePerAgeGroupValue, TimeframeOption } from '@corona-dashboard/common'; import { Spacer } from '~/components/base'; import { ErrorBoundary } from '~/components/error-boundary'; -import { - InteractiveLegend, - SelectOption, -} from '~/components/interactive-legend'; +import { InteractiveLegend, SelectOption } from '~/components/interactive-legend'; import { TimeSeriesChart } from '~/components/time-series-chart'; import { TimelineEventConfig } from '~/components/time-series-chart/components/timeline'; import { TooltipSeriesList } from '~/components/time-series-chart/components/tooltip/tooltip-series-list'; @@ -22,9 +14,7 @@ import { useBreakpoints } from '~/utils/use-breakpoints'; import { useList } from '~/utils/use-list'; import { BASE_SERIES_CONFIG } from './series-config'; -type NLHospitalAdmissionPerAgeGroupValue = - | NlIntensiveCareNicePerAgeGroupValue - | NlHospitalNicePerAgeGroupValue; +type NLHospitalAdmissionPerAgeGroupValue = NlIntensiveCareNicePerAgeGroupValue | NlHospitalNicePerAgeGroupValue; interface AdmissionsPerAgeGroup { values: NLHospitalAdmissionPerAgeGroupValue[]; @@ -37,12 +27,7 @@ interface AdmissionsPerAgeGroup { timelineEvents?: TimelineEventConfig[]; } -export function AdmissionsPerAgeGroup({ - values, - timeframe, - accessibility, - timelineEvents, -}: AdmissionsPerAgeGroup) { +export function AdmissionsPerAgeGroup({ values, timeframe, accessibility, timelineEvents }: AdmissionsPerAgeGroup) { const { commonTexts } = useIntl(); const { list, toggle, clear } = useList(); const breakpoints = useBreakpoints(true); @@ -50,71 +35,50 @@ export function AdmissionsPerAgeGroup({ const text = commonTexts.admissions_per_age_group_chart; const underReportedDateStart = getBoundaryDateStartUnix(values, 1); - const alwaysEnabled = ['admissions_overall_per_million']; /* Enrich config with dynamic data / locale */ - const seriesConfig: LineSeriesDefinition[] = - BASE_SERIES_CONFIG.map((baseAgeGroup) => { - const label = - baseAgeGroup.metricProperty in text.legend - ? text.legend[baseAgeGroup.metricProperty] - : baseAgeGroup.metricProperty; + const seriesConfig: LineSeriesDefinition[] = BASE_SERIES_CONFIG.map((baseAgeGroup) => { + const label = baseAgeGroup.metricProperty in text.legend ? text.legend[baseAgeGroup.metricProperty] : baseAgeGroup.metricProperty; - const ariaLabel = replaceVariablesInText( - commonTexts.aria_labels.age_old, - { - age: label, - } - ); - - return { - ...baseAgeGroup, - type: 'line', - shape: 'line', - label, - ariaLabel, - legendAriaLabel: ariaLabel, - }; + const ariaLabel = replaceVariablesInText(commonTexts.aria_labels.age_old, { + age: label, }); + return { + ...baseAgeGroup, + hideInLegend: true, + type: 'line', + shape: 'style' in baseAgeGroup ? baseAgeGroup.style : 'line', + label, + ariaLabel, + legendAriaLabel: ariaLabel, + }; + }); + /** * Chart: * - when nothing selected: all items * - otherwise: selected items + always enabled items */ - const compareList = list.concat(...alwaysEnabled); - const chartConfig = seriesConfig.filter( - (item) => - compareList.includes(item.metricProperty) || - compareList.length === alwaysEnabled.length - ); + const chartConfig = seriesConfig.filter((item) => list.includes(item.metricProperty) || list.length === 0); - const interactiveLegendOptions: SelectOption[] = seriesConfig.filter( - (item) => !alwaysEnabled.includes(item.metricProperty) - ); + const interactiveLegendOptions: SelectOption[] = seriesConfig; /* Conditionally let tooltip span over multiple columns */ const hasTwoColumns = list.length === 0 || list.length > 4; return ( - + ( - - )} + formatTooltip={(data) => } dataOptions={{ timespanAnnotations: [ { diff --git a/packages/app/src/domain/tested/infected-per-age-group/infected-per-age-group.tsx b/packages/app/src/domain/tested/infected-per-age-group/infected-per-age-group.tsx index 929627e7a8..11900dce1f 100644 --- a/packages/app/src/domain/tested/infected-per-age-group/infected-per-age-group.tsx +++ b/packages/app/src/domain/tested/infected-per-age-group/infected-per-age-group.tsx @@ -1,13 +1,7 @@ -import { - NlTestedPerAgeGroupValue, - TimeframeOption, -} from '@corona-dashboard/common'; +import { NlTestedPerAgeGroupValue, TimeframeOption } from '@corona-dashboard/common'; import { Spacer } from '~/components/base'; import { ErrorBoundary } from '~/components/error-boundary'; -import { - InteractiveLegend, - SelectOption, -} from '~/components/interactive-legend'; +import { InteractiveLegend, SelectOption } from '~/components/interactive-legend'; import { TimeSeriesChart } from '~/components/time-series-chart'; import { TimelineEventConfig } from '~/components/time-series-chart/components/timeline'; import { TooltipSeriesList } from '~/components/time-series-chart/components/tooltip/tooltip-series-list'; @@ -33,91 +27,63 @@ interface InfectedPerAgeGroup { text: SiteText['pages']['positive_tests_page']['shared']; } -export function InfectedPerAgeGroup({ - values, - timeframe, - accessibility, - timelineEvents, - text, -}: InfectedPerAgeGroup) { +export function InfectedPerAgeGroup({ values, timeframe, accessibility, timelineEvents, text }: InfectedPerAgeGroup) { const { commonTexts } = useIntl(); const { list, toggle, clear } = useList(); const breakpoints = useBreakpoints(true); const underReportedDateStart = getBoundaryDateStartUnix(values, 7); - const alwaysEnabled = ['infected_overall_per_100k']; /* Enrich config with dynamic data / locale */ - const seriesConfig: LineSeriesDefinition[] = - BASE_SERIES_CONFIG.map((baseAgeGroup) => { - const label = - baseAgeGroup.metricProperty in text.infected_per_age_group.legend - ? text.infected_per_age_group.legend[baseAgeGroup.metricProperty] - : baseAgeGroup.metricProperty; + const seriesConfig: LineSeriesDefinition[] = BASE_SERIES_CONFIG.map((baseAgeGroup) => { + const label = baseAgeGroup.metricProperty in text.infected_per_age_group.legend ? text.infected_per_age_group.legend[baseAgeGroup.metricProperty] : baseAgeGroup.metricProperty; - const ariaLabel = replaceVariablesInText( - commonTexts.aria_labels.age_old, - { - age: label, - } - ); - - return { - ...baseAgeGroup, - type: 'line', - shape: 'line', - label, - ariaLabel, - legendAriaLabel: ariaLabel, - }; + const ariaLabel = replaceVariablesInText(commonTexts.aria_labels.age_old, { + age: label, }); + return { + ...baseAgeGroup, + hideInLegend: true, + type: 'line', + shape: 'style' in baseAgeGroup ? baseAgeGroup.style : 'line', + label, + ariaLabel, + legendAriaLabel: ariaLabel, + }; + }); + /** * Chart: * - when nothing selected: all items * - otherwise: selected items + always enabled items */ - const compareList = list.concat(...alwaysEnabled); - const chartConfig = seriesConfig.filter( - (item) => - compareList.includes(item.metricProperty) || - compareList.length === alwaysEnabled.length - ); + const chartConfig = seriesConfig.filter((item) => list.includes(item.metricProperty) || list.length === 0); - const interactiveLegendOptions: SelectOption[] = seriesConfig.filter( - (item) => !alwaysEnabled.includes(item.metricProperty) - ); + const interactiveLegendOptions: SelectOption[] = seriesConfig; /* Conditionally let tooltip span over multiple columns */ const hasTwoColumns = list.length === 0 || list.length > 4; return ( - + ( - - )} + formatTooltip={(data) => } dataOptions={{ valueAnnotation: text.infected_per_age_group.value_annotation, timespanAnnotations: [ { start: underReportedDateStart, end: Infinity, - label: - text.infected_per_age_group.line_chart_legend_inaccurate_label, + label: text.infected_per_age_group.line_chart_legend_inaccurate_label, shortLabel: text.infected_per_age_group.tooltip_labels.inaccurate, }, ],