Improve the heatmap
This commit is contained in:
parent
058466294b
commit
f2dd382cf3
@ -1,6 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import * as dFns from "date-fns";
|
import * as dFns from "date-fns";
|
||||||
import * as R from "ramda";
|
|
||||||
|
|
||||||
interface DateValue {
|
interface DateValue {
|
||||||
date: Date;
|
date: Date;
|
||||||
@ -13,17 +12,12 @@ interface Props {
|
|||||||
data: DateValue[];
|
data: DateValue[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const GitHubCalendarHeatmap: React.FC<Props> = ({
|
function GitHubCalendarHeatmap({
|
||||||
startDate,
|
startDate,
|
||||||
endDate,
|
endDate,
|
||||||
data,
|
data,
|
||||||
}) => {
|
}: Props) {
|
||||||
// const numDays = dFns.differenceInDays(endDate, startDate);
|
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)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Calculate the number of weeks in the range
|
// Calculate the number of weeks in the range
|
||||||
const numWeeks = Math.ceil(numDays / 7);
|
const numWeeks = Math.ceil(numDays / 7);
|
||||||
@ -36,49 +30,48 @@ const GitHubCalendarHeatmap: React.FC<Props> = ({
|
|||||||
const totalHeight = 8 * (squareSize + padding) + weekTotalPadding; // Added an extra row for week total
|
const totalHeight = 8 * (squareSize + padding) + weekTotalPadding; // Added an extra row for week total
|
||||||
|
|
||||||
// Goal times for the daily and weekly squares
|
// Goal times for the daily and weekly squares
|
||||||
const dayGoalTime = 24;
|
const dayGoalTime = 8;
|
||||||
const weekGoalTime = 5 * dayGoalTime;
|
const weekGoalTime = 5 * dayGoalTime;
|
||||||
|
|
||||||
const getColorForValue = (value: number, goalTime: number): string => {
|
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 values = [
|
||||||
const color =
|
"#eeeeee",
|
||||||
saturation === 0 ? "lightgray" : `hsl(180, ${saturation}%, 50%)`; // Hue is fixed at 180 for a pleasant color. You can adjust this as needed.
|
"#d6e685",
|
||||||
return color;
|
"#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 getWeekTotal = (weekIndex: number): number => {
|
||||||
const weekStart = new Date(startDate);
|
const weekStart = dFns.addWeeks(dFns.startOfWeek(startDate, { weekStartsOn: 1 }), weekIndex);
|
||||||
weekStart.setDate(weekStart.getDate() + weekIndex * 7);
|
|
||||||
const weekEnd = new Date(weekStart);
|
|
||||||
weekEnd.setDate(weekEnd.getDate() + 6);
|
|
||||||
|
|
||||||
return data.reduce((total, {
|
return data.filter(dv => dFns.isSameWeek(dv.date, weekStart, { weekStartsOn: 1 }))
|
||||||
date,
|
.reduce((total, {count}) => total + count, 0);
|
||||||
count
|
|
||||||
}) => {
|
|
||||||
if (date >= weekStart && date <= weekEnd) {
|
|
||||||
return total + count;
|
|
||||||
}
|
|
||||||
return total;
|
|
||||||
}, 0);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const dateValues: DateValue[] = [];
|
const dateValues: DateValue[] = dFns.eachDayOfInterval({
|
||||||
|
start: startDate,
|
||||||
for (
|
end: endDate,
|
||||||
let date = new Date(startDate);
|
}).map((date) => {
|
||||||
date <= endDate;
|
|
||||||
date.setDate(date.getDate() + 1)
|
|
||||||
) {
|
|
||||||
const foundData = data.find(
|
const foundData = data.find(
|
||||||
({date: dataDate}) =>
|
({date: dataDate}) => dFns.isEqual(dFns.startOfDay(date), dFns.startOfDay(dataDate))
|
||||||
dataDate.toISOString().slice(0, 10) === date.toISOString().slice(0, 10)
|
|
||||||
);
|
);
|
||||||
dateValues.push({
|
|
||||||
date: new Date(date),
|
return {
|
||||||
|
date,
|
||||||
count: foundData ? foundData.count : 0,
|
count: foundData ? foundData.count : 0,
|
||||||
});
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<svg width={totalWidth} height={totalHeight}>
|
<svg width={totalWidth} height={totalHeight}>
|
||||||
@ -86,10 +79,9 @@ const GitHubCalendarHeatmap: React.FC<Props> = ({
|
|||||||
date,
|
date,
|
||||||
count
|
count
|
||||||
}) => {
|
}) => {
|
||||||
const weekIndex = Math.floor(
|
const weekIndex = dFns.differenceInCalendarWeeks(date, startDate, { weekStartsOn: 1 });
|
||||||
(date.getTime() - startDate.getTime()) / (1000 * 3600 * 24 * 7)
|
|
||||||
);
|
const dayOfWeek = (date.getDay() + 6) % 7;
|
||||||
const dayOfWeek = date.getDay();
|
|
||||||
const x = weekIndex * (squareSize + padding);
|
const x = weekIndex * (squareSize + padding);
|
||||||
const y = dayOfWeek * (squareSize + padding);
|
const y = dayOfWeek * (squareSize + padding);
|
||||||
const color = getColorForValue(count, dayGoalTime);
|
const color = getColorForValue(count, dayGoalTime);
|
||||||
@ -102,15 +94,18 @@ const GitHubCalendarHeatmap: React.FC<Props> = ({
|
|||||||
width={squareSize}
|
width={squareSize}
|
||||||
height={squareSize}
|
height={squareSize}
|
||||||
fill={color}
|
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) => {
|
.map((_, weekIndex) => {
|
||||||
const x = weekIndex * (squareSize + padding);
|
const x = weekIndex * (squareSize + padding);
|
||||||
const y = 7 * (squareSize + padding) + weekTotalPadding; // Position for the week total
|
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 (
|
return (
|
||||||
<rect
|
<rect
|
||||||
@ -120,11 +115,13 @@ const GitHubCalendarHeatmap: React.FC<Props> = ({
|
|||||||
width={squareSize}
|
width={squareSize}
|
||||||
height={squareSize}
|
height={squareSize}
|
||||||
fill={color}
|
fill={color}
|
||||||
|
data-tooltip-id={`calendar-tooltip`}
|
||||||
|
data-tooltip-content={count ? `Week total ${count.toFixed(2)} hours` : ``}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default GitHubCalendarHeatmap;
|
export default GitHubCalendarHeatmap;
|
||||||
|
|||||||
@ -9,19 +9,6 @@ import {Tooltip} from 'react-tooltip';
|
|||||||
import {Data} from "@/data/fetchData";
|
import {Data} from "@/data/fetchData";
|
||||||
import HeatMap from "@/components/HeatMap";
|
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) {
|
function useCalendarData(data: Data, initialDate: Date, endDate: Date) {
|
||||||
const timeEntries = data.timeEntries;
|
const timeEntries = data.timeEntries;
|
||||||
|
|
||||||
@ -71,19 +58,5 @@ export function CalendarOverviewCard({
|
|||||||
<Tooltip id="calendar-tooltip"/>
|
<Tooltip id="calendar-tooltip"/>
|
||||||
<Title>Overview</Title>
|
<Title>Overview</Title>
|
||||||
<HeatMap startDate={initialDate} endDate={endDate} data={calendarData}/>
|
<HeatMap startDate={initialDate} endDate={endDate} data={calendarData}/>
|
||||||
|
|
||||||
{/*<CalendarHeatmap*/}
|
|
||||||
{/* showWeekdayLabels={true}*/}
|
|
||||||
{/* startDate={initialDate}*/}
|
|
||||||
{/* endDate={endDate}*/}
|
|
||||||
{/* values={calendarData}*/}
|
|
||||||
{/* classForValue={value => `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*/}
|
|
||||||
{/* }}*/}
|
|
||||||
{/*/>*/}
|
|
||||||
</Card>
|
</Card>
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user