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

Commit

Permalink
Feature/cor 561 missing weekend data (#4202)
Browse files Browse the repository at this point in the history
* Add excluded data for the specific metric

* Add series

* Add missing in between

* Make line dashed and fill area with dates

* Add type fix

* Solved PR feedback
  • Loading branch information
Jorrik-Klijnsma-Work authored Apr 26, 2022
1 parent 56a4b38 commit da0bda6
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,20 @@ import { AreaClosed, LinePath } from '@visx/shape';
import { PositionScale } from '@visx/shape/lib/types';
import React from 'react';
import { useUniqueId } from '~/utils/use-unique-id';
import { curves, SeriesItem, SeriesSingleValue } from '../logic';
import {
curves,
SeriesItem,
SeriesSingleValue,
SeriesMissingValue,
isSeriesMissingValue,
} from '../logic';
import { useGappedSeries } from '../logic/use-gapped-series';

const DEFAULT_FILL_OPACITY = 0.2;
const DEFAULT_STROKE_WIDTH = 2;

type T = SeriesSingleValue | SeriesMissingValue;

type GappedAreaTrendProps = {
series: SeriesSingleValue[];
color: string;
Expand All @@ -18,6 +26,7 @@ type GappedAreaTrendProps = {
yScale: PositionScale;
curve?: 'linear' | 'step';
id: string;
isMissing: boolean;
};

export function GappedAreaTrend({
Expand All @@ -30,33 +39,69 @@ export function GappedAreaTrend({
yScale,
curve = 'linear',
id,
isMissing = false,
}: GappedAreaTrendProps) {
const gappedSeries = useGappedSeries(series);
const gappedSeriesMissing: T[][] = useGappedSeries(
series,
isMissing /*isMissing data (For example: the weekends and holidays)*/
);

return (
<>
{gappedSeries.map((series, index) => (
{gappedSeriesMissing.map((gappedSeries: T[], index) => (
<React.Fragment key={index}>
{isSeriesMissingValue(gappedSeries[0]) &&
gappedSeries[0].__hasMissing ? (
<LinePath
data={[gappedSeries[0], gappedSeries[gappedSeries.length - 1]]}
x={getX}
y={getY}
stroke={color}
strokeWidth={strokeWidth}
strokeLinecap="round"
strokeLinejoin="round"
strokeDasharray="5,5"
curve={curves[curve]}
/>
) : (
<LinePath
data={gappedSeries}
x={getX}
y={getY}
stroke={color}
strokeWidth={strokeWidth}
strokeLinecap="round"
strokeLinejoin="round"
curve={curves[curve]}
/>
)}
</React.Fragment>
))}
{gappedSeriesMissing.map((gappedSeries, index) => (
<React.Fragment key={index}>
<LinePath
data={series}
x={getX}
y={getY}
stroke={color}
strokeWidth={strokeWidth}
strokeLinecap="round"
strokeLinejoin="round"
curve={curves[curve]}
/>
<AreaClosed
data={series}
x={getX}
y={getY}
fill={color}
fillOpacity={fillOpacity}
curve={curves[curve]}
yScale={yScale}
id={`${id}_${index}`}
/>
{isSeriesMissingValue(gappedSeries[0]) &&
gappedSeries[0].__hasMissing ? (
<AreaClosed
data={gappedSeries}
x={getX}
y={getY}
fillOpacity={0}
curve={curves[curve]}
yScale={yScale}
id={`${id}_${index}`}
/>
) : (
<AreaClosed
data={gappedSeries}
x={getX}
y={getY}
fill={color}
fillOpacity={fillOpacity}
curve={curves[curve]}
yScale={yScale}
id={`${id}_${index}`}
/>
)}
</React.Fragment>
))}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ function SeriesUnmemoized<T extends TimestampedValue>({
getY={getY}
yScale={yScale}
id={id}
isMissing={config.metricProperty === 'beds_occupied_covid'}
/>
);
case 'bar':
Expand Down
13 changes: 11 additions & 2 deletions packages/app/src/components/time-series-chart/logic/series.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,9 @@ export type SeriesItem = {
export interface SeriesSingleValue extends SeriesItem {
__value?: number;
}
export interface SeriesMissingValue extends SeriesSingleValue {
__hasMissing?: boolean;
}
export interface SeriesDoubleValue extends SeriesItem {
__value_a?: number;
__value_b?: number;
Expand All @@ -347,18 +350,24 @@ export function isBarOutOfBounds<T extends TimestampedValue>(
}

export function isSeriesSingleValue(
value: SeriesSingleValue | SeriesDoubleValue
value: SeriesSingleValue | SeriesDoubleValue | SeriesMissingValue
): value is SeriesSingleValue {
return isDefined((value as any).__value);
}

export function isSeriesMissingValue(
value: SeriesSingleValue | SeriesDoubleValue | SeriesMissingValue
): value is SeriesMissingValue {
return isDefined((value as any).__hasMissing);
}

/**
* There are two types of trends. The normal single value trend and a double
* value type. Probably we can cover all TrendList here doesn't use the union
* with TimestampedValue as the LineChart because types got simplified in other
* places.
*/
export type SingleSeries = SeriesSingleValue[] | SeriesDoubleValue[];
export type SingleSeries = SeriesSingleValue[] | SeriesDoubleValue[] | SeriesMissingValue[];
export type SeriesList = SingleSeries[];

function getSeriesList<T extends TimestampedValue>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
isSeriesSingleValue,
SeriesDoubleValue,
SeriesSingleValue,
SeriesMissingValue,
} from './series';

/**
Expand All @@ -13,11 +14,31 @@ import {
* So a new array is created for each consecutive list of valid items.
*/
export function useGappedSeries<
T extends SeriesSingleValue | SeriesDoubleValue
>(series: T[]) {
T extends SeriesSingleValue | SeriesDoubleValue | SeriesMissingValue
>(series: T[], isMissing?: boolean) {

return useMemo(
() =>
series.reduce<T[][]>(
() => {
const DayTreshholdInSeconds = 130000;
const oneDay = 86400;

const getMissingDaysList = (startItem: SeriesSingleValue, endItem: SeriesSingleValue) => {
const missingDaysList: T[] = [];
const inBetweenSeconds = endItem.__date_unix - startItem.__date_unix - oneDay;
const missingDaysCount = Math.round(inBetweenSeconds / oneDay)
const firstDay = {__value: startItem.__value, __date_unix: startItem.__date_unix, __hasMissing: true} as SeriesMissingValue as T;
missingDaysList.push(firstDay);
for (let i = 1; i <= missingDaysCount; i++) {
const nextDate = startItem.__date_unix + (oneDay * i);
const newList = {__value: startItem.__value, __date_unix: nextDate, __hasMissing: true} as SeriesMissingValue as T;
missingDaysList.push(newList);
}
const lastDay = {__value: endItem.__value, __date_unix: endItem.__date_unix, __hasMissing: true} as SeriesMissingValue as T;
missingDaysList.push(lastDay);
return missingDaysList;
}

return series.reduce<T[][]>(
(lists, item) => {
const hasItemValue = isPresent(
isSeriesSingleValue(item) ? item.__value : item.__value_a
Expand All @@ -29,13 +50,26 @@ export function useGappedSeries<
lists.push(newList);
currentList = newList;
}
if (hasItemValue) {

// Get the previous value or the current one.
// The determain if days are skipped
// The current threshold is 1,5 days to take count for irregularity in data
const isLongerThanADay = last(currentList) ?? item;
if ((item.__date_unix - isLongerThanADay.__date_unix) > DayTreshholdInSeconds && (isMissing ?? false)) {
lists.push(getMissingDaysList(isLongerThanADay, item))
const newList: T[] = [item];
lists.push(newList);
currentList = [...newList];
currentList.push(item);
} else {
if (hasItemValue) {
currentList.push(item);
}
}

return lists;
},
[[]]
),
[series]
)}, [series, isMissing]
);
}

0 comments on commit da0bda6

Please sign in to comment.