feat: new heatmap in analytics

This commit is contained in:
sai80082 committed 2024-09-05 22:30:21 +05:30
1 parent d6bd5d2f6d
commit dbfb10b73a
6 files changed
+334 -16

No files matched your search

+3
View File
@@ -17,6 +17,7 @@
"@radix-ui/react-avatar": "^1.1.0",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-hover-card": "^1.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-popover": "^1.1.1",
@@ -30,6 +31,7 @@
"@tanstack/react-table": "^8.20.1",
"@types/cookie": "^0.6.0",
"@types/qs": "^6.9.15",
"@types/react-calendar-heatmap": "^1.6.7",
"axios": "^1.7.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
@@ -43,6 +45,7 @@
"pg": "^8.12.0",
"qs": "^6.13.0",
"react": "^18",
"react-calendar-heatmap": "^1.9.0",
"react-day-picker": "9.0.8",
"react-dom": "^18",
"react-grid-gallery": "^1.0.1",
@@ -0,0 +1,53 @@
import React from 'react';
// Function to generate the last 365 days of contribution data
const generateContributions = () => {
const contributions: number[][] = Array.from({ length: 7 }, () => Array(52).fill(0));
for (let week = 0; week < 52; week++) {
for (let day = 0; day < 7; day++) {
// Generate a random number of contributions for each day (0 to 5 for example)
contributions[day][week] = Math.floor(Math.random() * 6);
}
}
return contributions;
};
// Function to determine the background color based on the contribution count
const getColor = (count: number) => {
if (count === 0) return '';
if (count === 1) return 'bg-green-200';
if (count === 2) return 'bg-green-400';
if (count === 3) return 'bg-green-500';
if (count === 4) return 'bg-green-600';
return 'bg-green-800';
};
const ContributionGraph: React.FC = () => {
const contributions = generateContributions();
return (
<div className="p-4 w-full">
<h2 className="text-xl font-bold mb-4">GitHub Contribution Graph - Past Year</h2>
<div className="overflow-x-auto">
<table className="table-auto border-separate border-spacing-1 w-full">
<tbody>
{contributions.map((dayContributions, dayIndex) => (
<tr key={dayIndex}>
{dayContributions.map((count, weekIndex) => (
<td
key={weekIndex}
className={`h-6 ${getColor(count)} rounded-lg`}
style={{ width: `calc(${100 / 52}%)` }}
title={`${count} contributions`}
></td>
))}
</tr>
))}
</tbody>
</table>
</div>
</div>
);
};
export default ContributionGraph;
+27
View File
@@ -0,0 +1,27 @@
import * as React from "react"
import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
import { cn } from "@/lib/utils"
const HoverCard = HoverCardPrimitive.Root
const HoverCardTrigger = HoverCardPrimitive.Trigger
const HoverCardContent = React.forwardRef<
React.ElementRef<typeof HoverCardPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<HoverCardPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
))
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
export { HoverCard, HoverCardTrigger, HoverCardContent }
+100
View File
@@ -0,0 +1,100 @@
import { CartesianGrid, LabelList, Line, LineChart, XAxis, YAxis } from "recharts";
import {
ChartConfig,
ChartContainer,
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart";
import { useMemo } from "react";
import { CHART_COLORS } from "@/config/constants/chart.constant";
export interface ILineChartData {
label: string;
value: number;
}
interface ChartProps {
data: ILineChartData[];
topLabel?: string;
loading?: boolean;
errorMessage?: string | null;
}
export function LineChartDots({
data: _data,
topLabel,
loading,
errorMessage,
}: ChartProps) {
const data = useMemo(
() =>
_data.map((item, index) => ({
...item,
fill: CHART_COLORS[index] || "#000000",
})),
[_data]
);
const chartConfig = useMemo(() => {
let config: ChartConfig = {};
data.map((data) => {
config[data.label as string] = {
label: data.label,
color: "hsl(var(--chart-1))",
};
});
return config;
}, [data]);
if (loading) {
return <p>Loading...</p>;
}
if (errorMessage) {
return <p>{errorMessage}</p>;
}
return (
<ChartContainer config={chartConfig} className="min-h-[200px] w-full">
<LineChart
accessibilityLayer
data={data}
>
<CartesianGrid vertical={false} />
<YAxis /> {/* Added YAxis for the count */}
<XAxis padding={{ left: 20 }} />
<ChartTooltip
cursor={false}
content={
<ChartTooltipContent indicator="line" nameKey="label" hideLabel />
}
/>
<Line
dataKey="value"
type="natural"
stroke="#FF6B6B"
strokeWidth={2}
dot={{
fill: "var(--color-visitors)",
}}
activeDot={{
r: 6,
}}
>
<LabelList
position="top"
offset={12}
className="fill-foreground"
fontSize={12}
dataKey="label"
formatter={(value: keyof typeof chartConfig) =>
chartConfig[value]?.label
}
/>
</Line>
</LineChart>
</ChartContainer>
);
}
+78
View File
@@ -0,0 +1,78 @@
"use client"
import { TrendingUp } from "lucide-react"
import { PolarAngleAxis, PolarGrid, Radar, RadarChart } from "recharts"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import {
ChartConfig,
ChartContainer,
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart"
import { useMemo } from "react"
import { CHART_COLORS } from "@/config/constants/chart.constant"
export const description = "A radar chart with dots"
export interface IRadarChartData {
label: string;
value: number;
color?: string; // Add color property here
}
interface ChartProps {
data: IRadarChartData[];
topLabel?: string;
loading?: boolean;
errorMessage?: string | null;
}
const chartConfig = {
label: {
label: "label",
color: "#FF0000",
},
} satisfies ChartConfig
export default function RadarChartDots({ data: _data, topLabel, loading, errorMessage }: ChartProps) {
const chartData = _data
return (
<Card>
<CardContent className="pb-0 border-none">
<ChartContainer
config={chartConfig}
className="mx-auto aspect-square max-h-[250px] border-none"
>
<RadarChart data={chartData}>
<ChartTooltip cursor={false} content={<ChartTooltipContent />} />
<PolarAngleAxis dataKey="label" />
<PolarGrid stroke="#FFFFFF" strokeDasharray="3 3" /> {/* Set grid color to white */}
<Radar
dataKey="value"
fill="#FF0000"
fillOpacity={0.8}
dot={({ cx, cy, payload }) => (
<circle
cx={cx}
cy={cy}
r={4}
fill={payload.color} // Set color dynamically
fillOpacity={1}
/>
)}
/>
</RadarChart>
</ChartContainer>
</CardContent>
</Card>
)
}
+73 -16
View File
@@ -1,7 +1,18 @@
import { Inter } from "next/font/google";
import PageLayout from "@/components/layouts/PageLayout";
import Header from "@/components/shared/Header";
import EXIFDistribution, { IEXIFDistributionProps } from "@/components/analytics/exif/EXIFDistribution";
import EXIFDistribution, {
IEXIFDistributionProps,
} from "@/components/analytics/exif/EXIFDistribution";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import ContributionGraph from "@/components/analytics/exif/contributiongraph";
const inter = Inter({ subsets: ["latin"] });
@@ -42,36 +53,82 @@ const exifCharts: IEXIFDistributionProps[] = [
description: "Distribution of ISO",
},
{
column: 'exposureTime',
title: 'Exposure Time',
description: 'Distribution of exposure time'
column: "exposureTime",
title: "Exposure Time",
description: "Distribution of exposure time",
},
{
column: 'lensModel',
title: 'Lens Model',
description: 'Distribution of lens model'
column: "lensModel",
title: "Lens Model",
description: "Distribution of lens model",
},
{
column: 'projectionType',
title: 'Projection Type',
description: 'Distribution of projection type'
}
]
column: "projectionType",
title: "Projection Type",
description: "Distribution of projection type",
},
];
export default function ExifDataAnalytics() {
return (
<PageLayout>
<Header
leftComponent="Exif Data"
/>
<Header leftComponent="Exif Data" />
<div className="grid grid-cols-4 gap-4">
<Card>
<CardHeader>
<CardTitle className="text-center">Total Files</CardTitle>
</CardHeader>
<CardContent className="text-center">
{(() => {
const a = 700000;
return a.toLocaleString();
})()}
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-center">Images</CardTitle>
</CardHeader>
<CardContent className="text-center">
{(() => {
const a = 600000;
return a.toLocaleString();
})()}
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-center">Videos</CardTitle>
</CardHeader>
<CardContent className="text-center">
{(() => {
const a = 100000;
return a.toLocaleString();
})()}
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-center">Live</CardTitle>
</CardHeader>
<CardContent className="text-center">
{(() => {
const a = 50000;
return a.toLocaleString();
})()}
</CardContent>
</Card>
</div>
<ContributionGraph />
<div className="grid grid-cols-3 gap-4">
{exifCharts.map((chart) => (
<EXIFDistribution
<EXIFDistribution
key={chart.column}
column={chart.column}
title={chart.title}
description={chart.description}
/>
))}
</div>
</PageLayout>
);