67 lines
1.9 KiB
TypeScript
67 lines
1.9 KiB
TypeScript
"use client";
|
|
|
|
import { motion } from "framer-motion";
|
|
import type { HeatmapPoint } from "@/hooks/use-heatmap-data";
|
|
|
|
interface HeatmapLayerProps {
|
|
points: HeatmapPoint[];
|
|
projection: (coords: [number, number]) => [number, number] | null;
|
|
onPointClick?: (point: HeatmapPoint) => void;
|
|
}
|
|
|
|
export function HeatmapLayer({ points, projection, onPointClick }: HeatmapLayerProps) {
|
|
return (
|
|
<g>
|
|
{points.map((point, i) => {
|
|
const coords = projection([point.lng, point.lat]);
|
|
if (!coords) return null;
|
|
const [x, y] = coords;
|
|
const radius = Math.max(point.weight * 3, 4);
|
|
|
|
return (
|
|
<g key={`${point.city}-${point.country}-${i}`}>
|
|
{/* Glow */}
|
|
<motion.circle
|
|
cx={x}
|
|
cy={y}
|
|
r={radius * 2}
|
|
fill="rgba(0, 240, 255, 0.1)"
|
|
initial={{ scale: 0 }}
|
|
animate={{ scale: [1, 1.3, 1] }}
|
|
transition={{ duration: 2, repeat: Infinity, delay: i * 0.1 }}
|
|
/>
|
|
{/* Main dot */}
|
|
<motion.circle
|
|
cx={x}
|
|
cy={y}
|
|
r={radius}
|
|
fill="rgba(0, 240, 255, 0.6)"
|
|
stroke="rgba(0, 240, 255, 0.8)"
|
|
strokeWidth={1}
|
|
className="cursor-pointer"
|
|
initial={{ scale: 0 }}
|
|
animate={{ scale: 1 }}
|
|
transition={{ duration: 0.5, delay: i * 0.05 }}
|
|
whileHover={{ scale: 1.5 }}
|
|
onClick={() => onPointClick?.(point)}
|
|
/>
|
|
{/* Count label */}
|
|
{point.clawCount > 1 && (
|
|
<text
|
|
x={x}
|
|
y={y - radius - 4}
|
|
textAnchor="middle"
|
|
fill="#00f0ff"
|
|
fontSize={9}
|
|
fontFamily="var(--font-mono)"
|
|
>
|
|
{point.clawCount}
|
|
</text>
|
|
)}
|
|
</g>
|
|
);
|
|
})}
|
|
</g>
|
|
);
|
|
}
|