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

Feature/cor 1268 modified legends #4592

Merged
merged 5 commits into from
Jan 12, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 32 additions & 20 deletions packages/app/src/components/interactive-legend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export interface SelectOption<T = string> {
metricProperty: T;
label: string;
color: string;
shape?: 'line' | 'circle' | 'square' | 'gapped-area';
shape?: 'line' | 'circle' | 'square' | 'gapped-area' | 'dashed';
legendAriaLabel?: string;
}

Expand All @@ -22,13 +22,7 @@ interface InteractiveLegendProps<T = string> {
onReset?: () => void;
}

export function InteractiveLegend<T = string>({
helpText,
selectOptions,
selection,
onToggleItem,
onReset,
}: InteractiveLegendProps<T>) {
export function InteractiveLegend<T = string>({ helpText, selectOptions, selection, onToggleItem, onReset }: InteractiveLegendProps<T>) {
const { commonTexts } = useIntl();

const hasSelection = selection.length !== 0;
Expand All @@ -42,19 +36,17 @@ export function InteractiveLegend<T = string>({
const isSelected = selection.includes(item.metricProperty);
return (
<Item key={item.label}>
<StyledLabel
htmlFor={`checkboxgroup-${item.label}`}
isActive={hasSelection && isSelected}
borderColor={item.color}
data-text={item.label}
>
<StyledLabel htmlFor={`checkboxgroup-${item.label}`} isActive={hasSelection && isSelected} borderColor={item.color} data-text={item.label}>
{item.label}
{item.shape === 'line' && <Line color={item.color} />}
{item.shape === 'dashed' && (
<DashedContainer>
<Dashed color={item.color} />
</DashedContainer>
)}
{item.shape === 'circle' && <Circle color={item.color} />}
{item.shape === 'square' && <Square color={item.color} />}
{item.shape === 'gapped-area' && (
<GappedArea color={item.color} />
)}
{item.shape === 'gapped-area' && <GappedArea color={item.color} />}
</StyledLabel>
<StyledInput
type="checkbox"
Expand Down Expand Up @@ -124,9 +116,7 @@ const StyledLabel = styled.label<{
pl: 33,
py: 1,
borderRadius: '5px',
boxShadow: `inset 0px 0px 0px ${
isActive ? `3px ${borderColor}` : `1px ${colors.gray4}`
}`,
boxShadow: `inset 0px 0px 0px ${isActive ? `3px ${borderColor}` : `1px ${colors.gray4}`}`,
fontWeight: 'normal',
fontFamily: 'inherit',
fontSize: 1,
Expand Down Expand Up @@ -200,6 +190,28 @@ const ResetButton = styled.button<{ isVisible: boolean }>(({ isVisible }) =>
})
);

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 (
<svg width={15} height={15} viewBox={`0 0 ${15} ${15}`}>
<line stroke={color} strokeWidth={3} strokeDasharray={4} strokeLinecap="round" strokeLinejoin="round" x1={2} y1={15 / 2} x2={15 - 2} y2={15 / 2} />
</svg>
DariaKwork marked this conversation as resolved.
Show resolved Hide resolved
);
};

const Line = styled.div<{ color: string }>(({ color }) =>
DariaKwork marked this conversation as resolved.
Show resolved Hide resolved
css({
top: '50%',
Expand Down
69 changes: 20 additions & 49 deletions packages/app/src/components/legend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,7 @@ import css, { SystemStyleObject } from '@styled-system/css';
import { ReactNode } from 'react';
import styled from 'styled-components';

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 = {
Expand Down Expand Up @@ -44,15 +39,9 @@ export function Legend({ items, columns }: LegendProps) {
<Item key={i}>
{item.label}
{item.shape === 'square' && <Square color={item.color} />}
{item.shape === 'outlined-square' && (
<OutlinedSquare color={item.color} />
)}
{item.shape === 'dotted-square' && (
<DottedSquare color={item.color} />
)}
{item.shape === 'line' && (
<Line color={item.color} lineStyle={item.style ?? 'solid'} />
)}
{item.shape === 'outlined-square' && <OutlinedSquare color={item.color} />}
{item.shape === 'dotted-square' && <DottedSquare color={item.color} />}
{item.shape === 'line' && <Line color={item.color} lineStyle={item.style ?? 'solid'} />}
{item.shape === 'circle' && <Circle color={item.color} />}
</Item>
);
Expand Down Expand Up @@ -111,29 +100,12 @@ function DottedSquare({ color }: { color: string }) {
<Shape color="white" css={css({ top: '3px' })}>
<svg width={16} height={16} viewBox={`0 0 ${16} ${16}`}>
<defs>
<pattern
id="dotted_legend"
width="4"
height="4"
patternUnits="userSpaceOnUse"
>
<line
x1="0"
y1="4"
x2="0"
y2="0"
style={{ stroke: color, strokeWidth: 4, strokeDasharray: 2 }}
/>
<pattern id="dotted_legend" width="4" height="4" patternUnits="userSpaceOnUse">
<line x1="0" y1="4" x2="0" y2="0" style={{ stroke: color, strokeWidth: 4, strokeDasharray: 2 }} />
</pattern>
</defs>
<g>
<rect
x={0}
y={0}
fill={`url(#dotted_legend)`}
width={16}
height={16}
/>
<rect x={0} y={0} fill={`url(#dotted_legend)`} width={16} height={16} />
</g>
</svg>
</Shape>
Expand Down Expand Up @@ -170,18 +142,17 @@ 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.div<{ color: string; lineStyle: LegendLineStyle }>(({ color, lineStyle }) =>
DariaKwork marked this conversation as resolved.
Show resolved Hide resolved
css({
display: 'block',
position: 'absolute',
borderTopColor: color as SystemStyleObject,
borderTopStyle: lineStyle,
borderTopWidth: '3px',
top: '10px',
width: '15px',
height: 0,
borderRadius: '2px',
left: 0,
})
);
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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[];
Expand All @@ -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<string>();
const breakpoints = useBreakpoints(true);
Expand All @@ -53,68 +38,48 @@ export function AdmissionsPerAgeGroup({
const alwaysEnabled = ['admissions_overall_per_million'];
DariaKwork marked this conversation as resolved.
Show resolved Hide resolved

/* Enrich config with dynamic data / locale */
const seriesConfig: LineSeriesDefinition<NLHospitalAdmissionPerAgeGroupValue>[] =
BASE_SERIES_CONFIG.map((baseAgeGroup) => {
const label =
baseAgeGroup.metricProperty in text.legend
? text.legend[baseAgeGroup.metricProperty]
: baseAgeGroup.metricProperty;
const seriesConfig: LineSeriesDefinition<NLHospitalAdmissionPerAgeGroupValue>[] = 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,
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) => compareList.includes(item.metricProperty) || compareList.length === alwaysEnabled.length);

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 (
<ErrorBoundary>
<InteractiveLegend
helpText={text.legend_help_text}
selectOptions={interactiveLegendOptions}
selection={list}
onToggleItem={toggle}
onReset={clear}
/>
<InteractiveLegend helpText={text.legend_help_text} selectOptions={interactiveLegendOptions} selection={list} onToggleItem={toggle} onReset={clear} />
<Spacer mb={2} />
<TimeSeriesChart
disableLegend
DariaKwork marked this conversation as resolved.
Show resolved Hide resolved
accessibility={accessibility}
values={values}
timeframe={timeframe}
seriesConfig={chartConfig}
minHeight={breakpoints.md ? 300 : 250}
formatTooltip={(data) => (
<TooltipSeriesList data={data} hasTwoColumns={hasTwoColumns} />
)}
formatTooltip={(data) => <TooltipSeriesList data={data} hasTwoColumns={hasTwoColumns} />}
dataOptions={{
timespanAnnotations: [
{
Expand Down
Loading