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

Feature/COR-1170-rioolwaterpagina-verbetering-choropletenkaart-nl #4493

Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
726ee16
WIP
APW26 Nov 4, 2022
7ad030d
feat(sewer-choropleth): Implement tooltip notification for outdated data
APW26 Nov 7, 2022
676b0fa
feat(sewer-choropleth): Implement yellow color on map when data for t…
APW26 Nov 11, 2022
ae770be
feat(sewer-choropleth): Implements new legend item for outdated data,…
APW26 Nov 11, 2022
ae8ae4b
feat(sewer-choropleth): Cleanup and finalize outdated data tooltip ad…
APW26 Nov 11, 2022
997ddd1
feat(sewer-choropleth): Adjust tooltip to account for different data …
APW26 Nov 11, 2022
8ea2b15
feat(sewer-choropleth): Adds new outdated data property to schema for…
APW26 Nov 11, 2022
fcaa220
feat(sewer-choropleth): New sanity keys and data types.
APW26 Nov 11, 2022
5a5249d
Merge branch 'develop' into feature/COR-1147-Rioolwaterpagina-verbete…
APW26 Nov 11, 2022
6dc9de4
feat(sewer-choropleth): PR feedback round 1, and a few other fixes/re…
APW26 Nov 14, 2022
6709e8b
feat(sewer-choropleth): PR feedback round 2 - Add content for assisti…
APW26 Nov 14, 2022
9fda34d
feat(sewer-choropleth): PR feedback - Change condition for assigning …
APW26 Nov 16, 2022
33af3e7
Merge branch 'develop' into feature/COR-1147-Rioolwaterpagina-verbete…
APW26 Nov 16, 2022
d405f1a
feat(sewer-choropleth): Based on new info from backend, adding data_i…
APW26 Nov 21, 2022
04427e2
feat(sewer-choropleth): generate data types for new properties
APW26 Nov 21, 2022
3478a67
Merge branch 'feature/cor-1228-json-changes' into feature/COR-1147-Ri…
APW26 Nov 22, 2022
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
6 changes: 5 additions & 1 deletion packages/app/schema/gm_collection/sewer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"gmcode",
"average",
"total_installation_count",
"date_of_insertion_unix"
"date_of_insertion_unix",
"data_is_outdated"
],
"properties": {
"date_start_unix": {
Expand All @@ -30,6 +31,9 @@
},
"date_of_insertion_unix": {
"type": "integer"
},
"data_is_outdated": {
"type": "boolean"
}
}
}
5 changes: 4 additions & 1 deletion packages/app/schema/vr_collection/sewer.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"title": "vr_collection_sewer",
"type": "object",
"additionalProperties": false,
"required": ["date_unix", "vrcode", "average", "date_of_insertion_unix"],
"required": ["date_unix", "vrcode", "average", "date_of_insertion_unix", "data_is_outdated"],
"properties": {
"date_unix": {
"type": "integer"
Expand All @@ -17,6 +17,9 @@
},
"date_of_insertion_unix": {
"type": "integer"
},
"data_is_outdated": {
"type": "boolean"
}
}
}
54 changes: 27 additions & 27 deletions packages/app/src/components/choropleth-legenda.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
import { ChoroplethThresholdsValue } from '@corona-dashboard/common';
import { ChoroplethThresholdsValue, colors } from '@corona-dashboard/common';
import { css, SystemStyleObject } from '@styled-system/css';
import styled from 'styled-components';
import { ValueAnnotation } from '~/components/value-annotation';
import { useIntl } from '~/intl';
import { space, fontSizes } from '~/style/theme';
import { replaceVariablesInText } from '~/utils';
import { useBreakpoints } from '~/utils/use-breakpoints';
import { useResizeObserver } from '~/utils/use-resize-observer';
import { Box } from './base';
import { Legend, LegendItem } from './legend';
import { Text } from './typography';
import { InlineText, Text } from './typography';

interface ChoroplethLegendaProps {
title: string;
thresholds: ChoroplethThresholdsValue[];
valueAnnotation?: string;
type?: 'default' | 'bar';
pageType?: string;
outdatedDataLabel?: string;
}

export function ChoroplethLegenda({
title,
thresholds,
valueAnnotation,
type = 'bar',
}: ChoroplethLegendaProps) {
export function ChoroplethLegenda({ title, thresholds, valueAnnotation, type = 'bar', pageType, outdatedDataLabel }: ChoroplethLegendaProps) {
const [itemRef, itemSize] = useResizeObserver<HTMLLIElement>();
const [endLabelRef, endLabelSize] = useResizeObserver<HTMLSpanElement>();
const { commonTexts, formatNumber } = useIntl();
Expand All @@ -44,13 +42,18 @@ export function ChoroplethLegenda({
);

return (
<Box
width="100%"
pr={`${endLabelSize.width ?? 0 / 2}px`}
spacing={2}
aria-hidden="true"
>
<Box width="100%" paddingRight={`${endLabelSize.width ?? 0 / 2}px`} spacing={2} aria-hidden="true">
{title && <Text variant="subtitle1">{title}</Text>}

{pageType === 'sewer' && (
VWSCoronaDashboard26 marked this conversation as resolved.
Show resolved Hide resolved
<Box display="flex" alignItems="center" paddingBottom={space[2]}>
<LegendaColor first={true} width={`${100 / thresholds.length}%`} color={colors.yellow1} />
<Box paddingLeft={space[3]} fontSize={fontSizes[1]}>
<InlineText>{outdatedDataLabel}</InlineText>
</Box>
</Box>
)}

{'bar' === type ? (
<List>
{thresholds.map(({ color, threshold, label, endLabel }, index) => {
Expand All @@ -60,20 +63,11 @@ export function ChoroplethLegenda({
const formattedTreshold = formatNumber(threshold);

return (
<Item
key={color + threshold}
ref={index === 0 ? itemRef : undefined}
>
<Item key={color + threshold} ref={index === 0 ? itemRef : undefined} width={`${100 / thresholds.length}%`}>
<LegendaColor color={color} first={isFirst} last={isLast} />
{isFirst ? (
<StartLabel>{label ?? formattedTreshold}</StartLabel>
) : (
displayLabel && <Label>{label ?? formattedTreshold}</Label>
)}
{isFirst ? <StartLabel>{label ?? formattedTreshold}</StartLabel> : displayLabel && <Label>{label ?? formattedTreshold}</Label>}

{isLast && endLabel && (
<EndLabel ref={endLabelRef}>{endLabel}</EndLabel>
)}
{isLast && endLabel && <EndLabel ref={endLabelRef}>{endLabel}</EndLabel>}
</Item>
);
})}
Expand All @@ -96,23 +90,29 @@ const List = styled.ul(
})
);

const Item = styled.li(
// Assigning a flex-basis based on number of items in threshold so that legend items outside of the threshold array can be displayed using the same measurement.
const Item = styled.li<{
width: string;
}>(({ width }) =>
css({
flex: 1,
flexBasis: width,
position: 'relative',
})
);

const LegendaColor = styled.div<{
first?: boolean;
last?: boolean;
width?: string;
color: string;
}>((x) =>
css({
width: '100%',
height: '10px',
flexGrow: 0,
flexShrink: 0,
flexBasis: x.width,
borderRadius: x.first ? '2px 0 0 2px' : x.last ? '0 2px 2px 0' : 0,
backgroundColor: x.color,
})
Expand Down
51 changes: 12 additions & 39 deletions packages/app/src/components/choropleth-tile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,7 @@ import { ChoroplethLegenda } from '~/components/choropleth-legenda';
import { DataProps } from '~/types/attributes';
import { useBreakpoints } from '~/utils/use-breakpoints';
import { Box } from './base';
import {
ChartRegionControls,
RegionControlOption,
} from './chart-region-controls';
import { ChartRegionControls, RegionControlOption } from './chart-region-controls';
import { ErrorBoundary } from './error-boundary';
import { FullscreenChartTile } from './fullscreen-chart-tile';
import { MetadataProps } from './metadata';
Expand All @@ -20,10 +17,12 @@ type ChoroplethTileProps = DataProps & {
title: string;
thresholds: ChoroplethThresholdsValue[];
type?: 'default' | 'bar';
outdatedDataLabel?: string;
};
metadata?: MetadataProps;
valueAnnotation?: string;
hasPadding?: boolean;
pageType?: string;
} & (
| {
onChartRegionChange: (v: RegionControlOption) => void;
Expand All @@ -45,6 +44,7 @@ export function ChoroplethTile({
metadata,
valueAnnotation,
hasPadding,
pageType,
...dataProps
}: ChoroplethTileProps) {
const breakpoints = useBreakpoints(true);
Expand All @@ -55,40 +55,23 @@ export function ChoroplethTile({
title={legend.title}
valueAnnotation={valueAnnotation}
type={legend.type}
pageType={pageType}
outdatedDataLabel={legend.outdatedDataLabel}
/>
</Box>
);

return (
<FullscreenChartTile metadata={metadata}>
<Box
display="flex"
flexDirection={{ _: 'column', lg: 'row' }}
m={0}
as="figure"
{...dataProps}
height="100%"
spacing={3}
>
<Box display="flex" flexDirection={{ _: 'column', lg: 'row' }} m={0} as="figure" {...dataProps} height="100%" spacing={3}>
<Box flex={{ lg: 1 }} as="figcaption" spacing={3}>
<Heading level={3}>{title}</Heading>

{typeof description === 'string' ? (
<Text>{description}</Text>
) : (
description
)}
{typeof description === 'string' ? <Text>{description}</Text> : description}

{onChartRegionChange && chartRegion && (
<Box
display="flex"
justifyContent={{ _: 'center', lg: 'flex-start' }}
pt={4}
>
<ChartRegionControls
value={chartRegion}
onChange={onChartRegionChange}
/>
<Box display="flex" justifyContent={{ _: 'center', lg: 'flex-start' }} pt={4}>
<ChartRegionControls value={chartRegion} onChange={onChartRegionChange} />
</Box>
)}

Expand All @@ -99,18 +82,8 @@ export function ChoroplethTile({
)}
</Box>

<Box
flex={{ lg: 1 }}
display="flex"
flexDirection="column"
height="100%"
spacing={3}
>
<Box
height="100%"
mt={4}
pl={hasPadding && breakpoints.lg ? 4 : undefined}
>
<Box flex={{ lg: 1 }} display="flex" flexDirection="column" height="100%" spacing={3}>
<Box height="100%" mt={4} pl={hasPadding && breakpoints.lg ? 4 : undefined}>
<ErrorBoundary>{children}</ErrorBoundary>
</Box>

Expand Down
83 changes: 40 additions & 43 deletions packages/app/src/components/choropleth/logic/use-fill-color.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { assert, ChoroplethThresholdsValue } from '@corona-dashboard/common';
import { assert, ChoroplethThresholdsValue, colors } from '@corona-dashboard/common';
import { scaleThreshold } from 'd3-scale';
import { useMemo } from 'react';
import { isDefined, isPresent } from 'ts-is-present';
Expand All @@ -17,70 +17,73 @@ import { isCodedValueType } from './utils';
* @param dataConfig
* @returns
*/
export function useFillColor<T extends ChoroplethDataItem>(
data: T[],
map: MapType,
dataConfig: DataConfig<T>,
thresholdMap?: MapType
) {
export function useFillColor<T extends ChoroplethDataItem>(data: T[], map: MapType, dataConfig: DataConfig<T>, thresholdMap?: MapType) {
const codeType = mapToCodeType[map];
const { metricProperty, noDataFillColor } = dataConfig;

const getValueByCode = useMemo(
() => createGetValueByCode(metricProperty, codeType, data),
[metricProperty, codeType, data]
);
const getValueByCode = useMemo(() => createGetValueByCode(metricProperty, codeType, data), [metricProperty, codeType, data]);

const getIsOutdatedByCode = useMemo(() => createIsOutdatedByCode(codeType, data), [codeType, data]);

const threshold = thresholds[thresholdMap || map][metricProperty as string];
assert(
isDefined(threshold),
`[${
useFillColor.name
}] No threshold configured for map type ${map} and metric property ${metricProperty.toString()}`
);
assert(isDefined(threshold), `[${useFillColor.name}] No threshold configured for map type ${map} and metric property ${metricProperty.toString()}`);

const colorScale = useMemo(() => createColorScale(threshold), [threshold]);

return useMemo(
() => createGetFillColor(getValueByCode, colorScale, noDataFillColor),
[getValueByCode, colorScale, noDataFillColor]
);
return useMemo(() => createGetFillColor(getValueByCode, getIsOutdatedByCode, colorScale, noDataFillColor), [getValueByCode, getIsOutdatedByCode, colorScale, noDataFillColor]);
}

export function getFillColor<T extends ChoroplethDataItem>(
data: T[],
map: MapType,
dataConfig: DataConfig<T>,
thresholdMap?: MapType
) {
export function getFillColor<T extends ChoroplethDataItem>(data: T[], map: MapType, dataConfig: DataConfig<T>, thresholdMap?: MapType) {
const codeType = mapToCodeType[map];
const { metricProperty, noDataFillColor } = dataConfig;

const getValueByCode = createGetValueByCode(metricProperty, codeType, data);
const getIsOutdatedByCode = createIsOutdatedByCode(codeType, data);

const threshold = thresholds[thresholdMap || map][metricProperty as string];
assert(
isDefined(threshold),
`[${
getFillColor.name
}] No threshold configured for map type ${map} and metric property ${metricProperty.toString()}`
);
assert(isDefined(threshold), `[${getFillColor.name}] No threshold configured for map type ${map} and metric property ${metricProperty.toString()}`);
const colorScale = createColorScale(threshold);

return createGetFillColor(getValueByCode, colorScale, noDataFillColor);
return createGetFillColor(getValueByCode, getIsOutdatedByCode, colorScale, noDataFillColor);
}

function createGetFillColor(
getValueByCode: ReturnType<typeof createGetValueByCode>,
getIsOutdatedByCode: ReturnType<typeof createIsOutdatedByCode>,
colorScale: ReturnType<typeof createColorScale>,
noDataFillColor: string
) {
return (code: string) => {
const value = getValueByCode(code);
const result = isPresent(value) ? colorScale(value) : noDataFillColor;
const shouldOverrideDefaultColor = getIsOutdatedByCode(code);
let result = noDataFillColor;

if (isPresent(value)) result = colorScale(value);

// Override the default color scale when a datapoint contains outdated data.
if (shouldOverrideDefaultColor) result = colors.yellow1;

return result;
};
}

// Returns a function which returns a boolean when the datapoint (matching the code passed in) contains outdated data.
function createIsOutdatedByCode<T extends ChoroplethDataItem>(codeType: CodeProp, data: T[]): (code: string) => boolean {
return (code: string) => {
const dataPointByCode = data.find((dataPoint) => {
if ((dataPoint as unknown as Record<string, string>)[codeType] === code) {
return dataPoint;
}
});

if (dataPointByCode && 'data_is_outdated' in dataPointByCode) {
return dataPointByCode.data_is_outdated;
}

return false;
};
}

function createColorScale(threshold: ChoroplethThresholdsValue<number>[]) {
const domain = threshold.map((t) => t.threshold);
domain.shift();
Expand All @@ -91,11 +94,7 @@ function createColorScale(threshold: ChoroplethThresholdsValue<number>[]) {
return color;
}

function createGetValueByCode<T extends ChoroplethDataItem>(
metricProperty: keyof T,
codeType: CodeProp,
data: T[]
) {
function createGetValueByCode<T extends ChoroplethDataItem>(metricProperty: keyof T, codeType: CodeProp, data: T[]) {
return (code: string) => {
const item = data
.filter((x) => {
Expand All @@ -104,8 +103,6 @@ function createGetValueByCode<T extends ChoroplethDataItem>(
})
.find((x) => (x as unknown as Record<string, string>)[codeType] === code);

return isDefined(item) && isPresent(item[metricProperty])
? Number(item[metricProperty])
: undefined;
return isDefined(item) && isPresent(item[metricProperty]) ? Number(item[metricProperty]) : undefined;
};
}
Loading