feat: new heatmap in analytics
This commit is contained in:
1 parent
d6bd5d2f6d
commit
dbfb10b73a
6 files changed
+334
-16
No files matched your search
@@ -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;
|
||||
@@ -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 }
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user