init
This commit is contained in:
66
components/map/heatmap-layer.tsx
Normal file
66
components/map/heatmap-layer.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
"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.lobsterCount > 1 && (
|
||||
<text
|
||||
x={x}
|
||||
y={y - radius - 4}
|
||||
textAnchor="middle"
|
||||
fill="#00f0ff"
|
||||
fontSize={9}
|
||||
fontFamily="var(--font-mono)"
|
||||
>
|
||||
{point.lobsterCount}
|
||||
</text>
|
||||
)}
|
||||
</g>
|
||||
);
|
||||
})}
|
||||
</g>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user