diff --git a/scripts/compile.ts b/scripts/compile.ts index 9bca98f..b1774e9 100644 --- a/scripts/compile.ts +++ b/scripts/compile.ts @@ -70,6 +70,35 @@ const targetStatisticsDaily = (inputs: ResponseActivityRide[]) => const targetStatisticsMonthly = (inputs: ResponseActivityRide[]) => groupStatistics(inputs, date => timestampToIso(date).split('-').slice(0, 2).join('-')); +const WEEKDAYS = [ + 'Monday', + 'Tuesday', + 'Wednesday', + 'Thursday', + 'Friday', + 'Saturday', + 'Sunday', +]; + +const targetStatisticsWeekly = (inputs: ResponseActivityRide[]) => { + const daily = groupStatistics(inputs, date => timestampToIso(date)); + const dailyDistances: { count: number, distance: number }[][] = _.list(0, WEEKDAYS.length - 1, () => []); + Object.entries(daily).forEach(([isoDate, values]) => dailyDistances[(WEEKDAYS.length + new Date(isoDate).getDay() - 1) % WEEKDAYS.length].push(values)); + const totalWeeks = _.max(dailyDistances.map(array => array.length)) || 0; + return Object.fromEntries(WEEKDAYS.map((label, i) => { + const countAndDistancesBase = dailyDistances[i]; + // Correct for empty days + const countAndDistances = countAndDistancesBase.concat(_.list(0, totalWeeks - countAndDistancesBase.length - 1, () => ({ count: 0, distance: 0 }))); + const count = computeMeanStdDev(countAndDistances.map(v => v.count)), distance = computeMeanStdDev(countAndDistances.map(v => v.distance)); + return [label, { + meanCount: count.mean, + stdDevCount: count.stdDev, + meanDistance: distance.mean, + stdDevDistance: distance.stdDev, + }]; + })); +}; + const targetCumulativeDistance = (inputs: ResponseActivityRide[]) => { const daily = Object.entries(targetStatisticsDaily(inputs)); const array: [string, number][] = []; @@ -268,6 +297,7 @@ const compile = () => { const targets = { statisticsDaily: targetStatisticsDaily(inputs), statisticsMonthly: targetStatisticsMonthly(inputs), + statisticsWeekly: targetStatisticsWeekly(inputs), records: targetRecords(inputs), cumulativeDistance: targetCumulativeDistance(inputs), cadence: targetCadence(inputs), diff --git a/src/Visualizations.tsx b/src/Visualizations.tsx index 13afa18..763bffb 100644 --- a/src/Visualizations.tsx +++ b/src/Visualizations.tsx @@ -10,6 +10,7 @@ import { Grid, GridItem } from '@chakra-ui/react'; import { SpeedDistribution } from './viz/SpeedDistribution'; import { PowerDistribution } from './viz/PowerDistribution'; import { SpeedAccelerationChart } from './viz/SpeedAccelerationChart'; +import { WeeklyChart } from './viz/WeeklyChart'; export const Visualizations: React.FC = () => { const largeProps = { colSpan: 2 } @@ -22,12 +23,15 @@ export const Visualizations: React.FC = () => { - + + + + diff --git a/src/types/types.ts b/src/types/types.ts index 143b6d2..ae5972f 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -1,6 +1,7 @@ export interface Data { statisticsDaily: TargetStatistics; - statisticsMonthly: TargetStatistics; + statisticsMonthly: Record; + statisticsWeekly: Record; records: TargetRecords; cumulativeDistance: TargetCumulative; cadence: TargetBuckets; @@ -15,6 +16,13 @@ export interface TargetStatistics { distance: number; } +export interface TargetStatisticsAggregated { + meanCount: number, + stdDevCount: number, + meanDistance: number, + stdDevDistance: number, +} + export interface TargetRecords { totalDistance: number; maxSpeed: number; diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..27149c0 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,5 @@ +// Percentile to Z-score mapping for some well-known values +export const Z_SCORES = { + 50: 0.674, + 95: 1.96, +} as const; diff --git a/src/viz/MonthlyChart.tsx b/src/viz/MonthlyChart.tsx index bc3871e..cd1feae 100644 --- a/src/viz/MonthlyChart.tsx +++ b/src/viz/MonthlyChart.tsx @@ -2,26 +2,31 @@ import { useDataQuery } from '../hooks/useDataQuery'; import React from 'react'; import { TargetStatistics } from '../types/types'; import { Bar, BarChart, CartesianGrid, ResponsiveContainer, XAxis, YAxis } from 'recharts'; -import { Box, Heading, useColorModeValue, useTheme } from '@chakra-ui/react'; +import { Box, Heading, Text, useColorModeValue, useTheme } from '@chakra-ui/react'; // eslint-disable-next-line @typescript-eslint/no-unused-vars const MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; interface MonthlyChartContentProps { - data: TargetStatistics; + data: Record; } const MonthlyChartContent: React.FC = ({ data }) => { const theme = useTheme(); const color = useColorModeValue(theme.colors.blue[500], theme.colors.blue[200]); - const barData = Object.entries(data).map(([key, obj]) => ({ name: key, ...obj, distance: obj.distance / 1000 })).reverse().slice(0, 12).reverse(); + const lastMonths = 12; + + const barData = Object.entries(data).map(([key, obj]) => ({ name: key, ...obj, distance: obj.distance / 1000 })).reverse().slice(0, lastMonths).reverse(); return ( - + Monthly distance + + Last {lastMonths} months + = ({ data }) => { > - + diff --git a/src/viz/SpeedAccelerationChart.tsx b/src/viz/SpeedAccelerationChart.tsx index 468819c..c28239c 100644 --- a/src/viz/SpeedAccelerationChart.tsx +++ b/src/viz/SpeedAccelerationChart.tsx @@ -11,12 +11,7 @@ import { XAxis, YAxis, } from 'recharts'; - -// Percentile to Z-score mapping for some well-known values -const Z_SCORES = { - 50: 0.674, - 95: 1.96, -} as const; +import { Z_SCORES } from '../utils'; interface SpeedAccelerationChartContentProps { data: TargetAccelerations; @@ -40,7 +35,7 @@ const SpeedAccelerationChartContent: React.FC - With a {percentile}% confidence interval + {percentile}% confidence interval ; +} + +const WeeklyChartContent: React.FC = ({ data }) => { + const theme = useTheme(); + const color = useColorModeValue(theme.colors.blue[500], theme.colors.blue[200]); + const errorColor = useColorModeValue(theme.colors.blue[700], theme.colors.blue[50]); + + const percentile = 50 as const; + const zScore = Z_SCORES[percentile]; + + const barData = Object.entries(data).map(([key, obj]) => ({ weekday: key.substring(0, 1), ...obj, meanDistance: obj.meanDistance / 1000, confidenceDistance: zScore * obj.stdDevDistance / 1000 })); + + return ( + + + Weekly distance + + + {percentile}% confidence interval + + + + + + + + + + + + + ); +}; + +export const WeeklyChart = () => { + const { data } = useDataQuery('statisticsWeekly'); + + if (!data) { + return null; + } + return ( + + ) +};