Initial import of AI heatmap
This commit is contained in:
parent
cf1788ebcd
commit
058466294b
130
src/components/HeatMap.tsx
Normal file
130
src/components/HeatMap.tsx
Normal file
@ -0,0 +1,130 @@
|
||||
import React from "react";
|
||||
import * as dFns from "date-fns";
|
||||
import * as R from "ramda";
|
||||
|
||||
interface DateValue {
|
||||
date: Date;
|
||||
count: number;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
data: DateValue[];
|
||||
}
|
||||
|
||||
const GitHubCalendarHeatmap: React.FC<Props> = ({
|
||||
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)
|
||||
);
|
||||
|
||||
// Calculate the number of weeks in the range
|
||||
const numWeeks = Math.ceil(numDays / 7);
|
||||
|
||||
// Calculate the size of each square in the heatmap and the padding between each square
|
||||
const squareSize = 15;
|
||||
const padding = 2;
|
||||
const weekTotalPadding = 10; // Extra padding for the week total row
|
||||
const totalWidth = numWeeks * (squareSize + padding);
|
||||
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 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 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);
|
||||
|
||||
return data.reduce((total, {
|
||||
date,
|
||||
count
|
||||
}) => {
|
||||
if (date >= weekStart && date <= weekEnd) {
|
||||
return total + count;
|
||||
}
|
||||
return total;
|
||||
}, 0);
|
||||
};
|
||||
|
||||
const dateValues: DateValue[] = [];
|
||||
|
||||
for (
|
||||
let date = new Date(startDate);
|
||||
date <= endDate;
|
||||
date.setDate(date.getDate() + 1)
|
||||
) {
|
||||
const foundData = data.find(
|
||||
({date: dataDate}) =>
|
||||
dataDate.toISOString().slice(0, 10) === date.toISOString().slice(0, 10)
|
||||
);
|
||||
dateValues.push({
|
||||
date: new Date(date),
|
||||
count: foundData ? foundData.count : 0,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<svg width={totalWidth} height={totalHeight}>
|
||||
{dateValues.map(({
|
||||
date,
|
||||
count
|
||||
}) => {
|
||||
const weekIndex = Math.floor(
|
||||
(date.getTime() - startDate.getTime()) / (1000 * 3600 * 24 * 7)
|
||||
);
|
||||
const dayOfWeek = date.getDay();
|
||||
const x = weekIndex * (squareSize + padding);
|
||||
const y = dayOfWeek * (squareSize + padding);
|
||||
const color = getColorForValue(count, dayGoalTime);
|
||||
|
||||
return (
|
||||
<rect
|
||||
key={date.toISOString()}
|
||||
x={x}
|
||||
y={y}
|
||||
width={squareSize}
|
||||
height={squareSize}
|
||||
fill={color}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{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);
|
||||
|
||||
return (
|
||||
<rect
|
||||
key={`week-total-${weekIndex}`}
|
||||
x={x}
|
||||
y={y}
|
||||
width={squareSize}
|
||||
height={squareSize}
|
||||
fill={color}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default GitHubCalendarHeatmap;
|
||||
@ -7,6 +7,7 @@ import 'react-calendar-heatmap/dist/styles.css';
|
||||
import '../../app/calendar-styles.css'
|
||||
import {Tooltip} from 'react-tooltip';
|
||||
import {Data} from "@/data/fetchData";
|
||||
import HeatMap from "@/components/HeatMap";
|
||||
|
||||
const granularity = 4;
|
||||
|
||||
@ -69,18 +70,20 @@ export function CalendarOverviewCard({
|
||||
return <Card className="col-span-1">
|
||||
<Tooltip id="calendar-tooltip"/>
|
||||
<Title>Overview</Title>
|
||||
<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
|
||||
}}
|
||||
/>
|
||||
<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>
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user