From f2dd382cf3c5b5a3aa55bab06b03555e40965d99 Mon Sep 17 00:00:00 2001 From: Joshua Coles Date: Sun, 25 Feb 2024 19:53:58 +0000 Subject: [PATCH] Improve the heatmap --- src/components/HeatMap.tsx | 99 +++++++++---------- src/components/cards/calendarOverviewCard.tsx | 27 ----- 2 files changed, 48 insertions(+), 78 deletions(-) diff --git a/src/components/HeatMap.tsx b/src/components/HeatMap.tsx index 51922dc..0b1cd08 100644 --- a/src/components/HeatMap.tsx +++ b/src/components/HeatMap.tsx @@ -1,6 +1,5 @@ import React from "react"; import * as dFns from "date-fns"; -import * as R from "ramda"; interface DateValue { date: Date; @@ -13,17 +12,12 @@ interface Props { data: DateValue[]; } -const GitHubCalendarHeatmap: React.FC = ({ - startDate, - endDate, - data, - }) => { - // const numDays = dFns.differenceInDays(endDate, startDate); - - // Calculate the number of days in the range - const numDays = Math.ceil( - (endDate.getTime() - startDate.getTime()) / (1000 * 3600 * 24) - ); +function GitHubCalendarHeatmap({ + startDate, + endDate, + data, + }: Props) { + const numDays = dFns.differenceInDays(endDate, startDate); // Calculate the number of weeks in the range const numWeeks = Math.ceil(numDays / 7); @@ -36,49 +30,48 @@ const GitHubCalendarHeatmap: React.FC = ({ const totalHeight = 8 * (squareSize + padding) + weekTotalPadding; // Added an extra row for week total // Goal times for the daily and weekly squares - const dayGoalTime = 24; + const dayGoalTime = 8; const weekGoalTime = 5 * dayGoalTime; const getColorForValue = (value: number, goalTime: number): string => { - const saturation = Math.min(Math.max(value / goalTime, 0), 1) * 100; // Normalize value to range [0, 1] and then convert to percentage - const color = - saturation === 0 ? "lightgray" : `hsl(180, ${saturation}%, 50%)`; // Hue is fixed at 180 for a pleasant color. You can adjust this as needed. - return color; + const values = [ + "#eeeeee", + "#d6e685", + "#8cc665", + "#44a340", + "#1e6823", + ] + + const linearValue = Math.round((value / goalTime) * 4); + + // If we did something, but not enough to reach the first level, return 1 + if (linearValue == 0 && value > 0) return values[1]; + + // Clamp to the granularity + if (linearValue > 4) return values[4]; + return values[linearValue]; }; const getWeekTotal = (weekIndex: number): number => { - const weekStart = new Date(startDate); - weekStart.setDate(weekStart.getDate() + weekIndex * 7); - const weekEnd = new Date(weekStart); - weekEnd.setDate(weekEnd.getDate() + 6); + const weekStart = dFns.addWeeks(dFns.startOfWeek(startDate, { weekStartsOn: 1 }), weekIndex); - return data.reduce((total, { - date, - count - }) => { - if (date >= weekStart && date <= weekEnd) { - return total + count; - } - return total; - }, 0); + return data.filter(dv => dFns.isSameWeek(dv.date, weekStart, { weekStartsOn: 1 })) + .reduce((total, {count}) => total + count, 0); }; - const dateValues: DateValue[] = []; - - for ( - let date = new Date(startDate); - date <= endDate; - date.setDate(date.getDate() + 1) - ) { + const dateValues: DateValue[] = dFns.eachDayOfInterval({ + start: startDate, + end: endDate, + }).map((date) => { const foundData = data.find( - ({date: dataDate}) => - dataDate.toISOString().slice(0, 10) === date.toISOString().slice(0, 10) + ({date: dataDate}) => dFns.isEqual(dFns.startOfDay(date), dFns.startOfDay(dataDate)) ); - dateValues.push({ - date: new Date(date), + + return { + date, count: foundData ? foundData.count : 0, - }); - } + } + }); return ( @@ -86,10 +79,9 @@ const GitHubCalendarHeatmap: React.FC = ({ date, count }) => { - const weekIndex = Math.floor( - (date.getTime() - startDate.getTime()) / (1000 * 3600 * 24 * 7) - ); - const dayOfWeek = date.getDay(); + const weekIndex = dFns.differenceInCalendarWeeks(date, startDate, { weekStartsOn: 1 }); + + const dayOfWeek = (date.getDay() + 6) % 7; const x = weekIndex * (squareSize + padding); const y = dayOfWeek * (squareSize + padding); const color = getColorForValue(count, dayGoalTime); @@ -102,15 +94,18 @@ const GitHubCalendarHeatmap: React.FC = ({ width={squareSize} height={squareSize} fill={color} + data-tooltip-id={`calendar-tooltip`} + data-tooltip-content={count ? `${dFns.format(date, 'EEE do')}: ${count.toFixed(2)} hours` : `${dFns.format(date, 'EEE do')}`} /> ); })} - {Array(numWeeks) - .fill(0) + + {Array(numWeeks).fill(0) .map((_, weekIndex) => { const x = weekIndex * (squareSize + padding); const y = 7 * (squareSize + padding) + weekTotalPadding; // Position for the week total - const color = getColorForValue(getWeekTotal(weekIndex), weekGoalTime); + const count = getWeekTotal(weekIndex); + const color = getColorForValue(count, weekGoalTime); return ( = ({ width={squareSize} height={squareSize} fill={color} + data-tooltip-id={`calendar-tooltip`} + data-tooltip-content={count ? `Week total ${count.toFixed(2)} hours` : ``} /> ); })} ); -}; +} export default GitHubCalendarHeatmap; diff --git a/src/components/cards/calendarOverviewCard.tsx b/src/components/cards/calendarOverviewCard.tsx index 481419d..791a67e 100644 --- a/src/components/cards/calendarOverviewCard.tsx +++ b/src/components/cards/calendarOverviewCard.tsx @@ -9,19 +9,6 @@ import {Tooltip} from 'react-tooltip'; import {Data} from "@/data/fetchData"; import HeatMap from "@/components/HeatMap"; -const granularity = 4; - -function computeCompletionShade(value: number, dailyGoal: number) { - const linearValue = Math.round((value / dailyGoal) * granularity); - - // If we did something, but not enough to reach the first level, return 1 - if (linearValue == 0 && value > 0) return 1; - - // Clamp to the granularity - if (linearValue > granularity) return granularity; - return linearValue; -} - function useCalendarData(data: Data, initialDate: Date, endDate: Date) { const timeEntries = data.timeEntries; @@ -71,19 +58,5 @@ export function CalendarOverviewCard({ Overview - - {/* `color-github-${computeCompletionShade(value?.count ?? 0, goal)}`}*/} - {/* tooltipDataAttrs={(value: any) => {*/} - {/* return value.date ? {*/} - {/* 'data-tooltip-id': `calendar-tooltip`,*/} - {/* 'data-tooltip-content': value.count ? `${dFns.format(value.date, 'EEE do')}: ${value.count.toFixed(2)} hours` : `${dFns.format(value.date, 'EEE do')}`*/} - {/* } : undefined*/} - {/* }}*/} - {/*/>*/} }