Download src/components/WeeklyHeatmap.tsx from hugging-science/science-release-map: direct link, hf CLI and curl.
- Browser
- Download file 4.54 kB
-
https://huggingface.co/spaces/hugging-science/science-release-map/resolve/main/src/components/WeeklyHeatmap.tsx
- Command line
-
hf download hf://spaces/hugging-science/science-release-map/src/components/WeeklyHeatmap.tsx
-
curl -L -o WeeklyHeatmap.tsx https://huggingface.co/spaces/hugging-science/science-release-map/resolve/main/src/components/WeeklyHeatmap.tsx
4.54 kB
| import React, { useMemo, useCallback, useEffect, useState } from "react"; | |
| import { Tooltip } from "@mui/material"; | |
| import { getWeekDateRange } from "../utils/weeklyCalendar"; | |
| import { getHeatmapColorIntensity } from "../utils/heatmapColors"; | |
| type WeeklyActivity = { | |
| date: string; | |
| count: number; | |
| level: number; | |
| items?: string[]; | |
| }; | |
| type WeeklyHeatmapProps = { | |
| data: WeeklyActivity[]; | |
| color: string; | |
| }; | |
| const WeeklyHeatmap: React.FC<WeeklyHeatmapProps> = ({ data, color }) => { | |
| // Track theme changes | |
| const [isDark, setIsDark] = useState(false); | |
| useEffect(() => { | |
| const checkTheme = () => { | |
| setIsDark(document.documentElement.classList.contains('dark')); | |
| }; | |
| // Initial check | |
| checkTheme(); | |
| // Watch for theme changes | |
| const observer = new MutationObserver(checkTheme); | |
| observer.observe(document.documentElement, { | |
| attributes: true, | |
| attributeFilter: ['class'] | |
| }); | |
| return () => observer.disconnect(); | |
| }, []); | |
| // Memoize the grouped data to avoid recalculation | |
| const groupedData = useMemo(() => { | |
| return data.reduce((acc, activity) => { | |
| const date = new Date(activity.date); | |
| const yearMonth = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; | |
| if (!acc[yearMonth]) { | |
| acc[yearMonth] = []; | |
| } | |
| acc[yearMonth].push(activity); | |
| return acc; | |
| }, {} as Record<string, WeeklyActivity[]>); | |
| }, [data]); | |
| // Memoize sorted months | |
| const sortedMonths = useMemo(() => Object.keys(groupedData).sort(), [groupedData]); | |
| // Memoize the color intensity function with theme awareness | |
| const getColorIntensity = useCallback((level: number) => { | |
| return getHeatmapColorIntensity(level, color, isDark) || undefined; | |
| }, [color, isDark]); | |
| // Get the exact same empty dot color as ActivityCalendar | |
| const emptyDotColor = useMemo(() => { | |
| return isDark ? '#374151' : '#d1d5db'; | |
| }, [isDark]); | |
| // Get month names | |
| const getMonthName = (yearMonth: string) => { | |
| const [year, month] = yearMonth.split('-'); | |
| const date = new Date(parseInt(year), parseInt(month) - 1); | |
| return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }); | |
| }; | |
| return ( | |
| <div className="w-full"> | |
| <div className="flex flex-wrap gap-4 justify-center"> | |
| {sortedMonths.map((yearMonth) => { | |
| const monthData = groupedData[yearMonth]; | |
| return ( | |
| <div key={yearMonth} className="flex flex-col items-center"> | |
| <div className="text-xs mb-2 text-gray-600 dark:text-gray-400"> | |
| {getMonthName(yearMonth)} | |
| </div> | |
| <div className="flex gap-1"> | |
| {monthData.map((activity, index) => { | |
| const itemsText = activity.items && activity.items.length > 0 | |
| ? activity.items.join(', ') | |
| : 'No releases'; | |
| const tooltipTitle = activity.count > 0 | |
| ? `${activity.count} new repos in week of ${getWeekDateRange(activity.date)}: ${itemsText}` | |
| : `No repos in week of ${getWeekDateRange(activity.date)}`; | |
| return ( | |
| <Tooltip | |
| key={`${yearMonth}-${index}`} | |
| title={tooltipTitle} | |
| arrow | |
| > | |
| <div | |
| className="w-3 h-3 rounded-sm cursor-pointer transition-opacity hover:opacity-80" | |
| style={{ | |
| backgroundColor: activity.level === 0 ? emptyDotColor : getColorIntensity(activity.level), | |
| }} | |
| /> | |
| </Tooltip> | |
| ); | |
| })} | |
| </div> | |
| </div> | |
| ); | |
| })} | |
| </div> | |
| {/* Legend */} | |
| <div className="flex items-center justify-center mt-4 text-xs text-gray-600 dark:text-gray-400"> | |
| <span className="mr-2">Less</span> | |
| <div className="flex gap-1"> | |
| {[0, 1, 2, 3, 4].map((level) => ( | |
| <div | |
| key={level} | |
| className="w-2.5 h-2.5 rounded-sm" | |
| style={{ | |
| backgroundColor: level === 0 ? emptyDotColor : getColorIntensity(level), | |
| }} | |
| /> | |
| ))} | |
| </div> | |
| <span className="ml-2">More</span> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| export default React.memo(WeeklyHeatmap); |