'use client'; import './world-map.scss'; import { useMemo, useState } from 'react'; import { EXCHANGE_GEO, moveDir, formatFlucRate, formatIndexClose, type WorldIndexRow } from '@/types/worldIndex'; // 등거리(plate carrée) 투영 — viewBox 1000×500 (경도 -180~180, 위도 90~-90) const VW = 1000; const VH = 500; const project = (lat: number, lng: number) => ({ x: ((lng + 180) / 360) * VW, y: ((90 - lat) / 180) * VH }); // 스타일라이즈드(저폴리) 대륙 실루엣 — 지리적 근사(정밀 지도 아님). viewBox 좌표계 직접 좌표. const CONTINENTS: string[] = [ // 북미 '140,84 210,66 302,70 314,104 288,126 302,158 274,176 250,156 248,196 226,210 200,236 188,222 212,198 214,172 190,156 166,130 150,106', // 그린란드 '330,54 366,48 380,74 352,92 332,76', // 남미 '300,266 340,258 364,278 380,300 366,336 344,384 320,414 304,420 296,388 300,342 290,300 292,278', // 유럽 '470,92 518,84 550,94 560,116 540,134 512,150 496,134 480,142 470,116', // 아프리카 '486,176 548,166 588,188 596,236 570,300 542,358 520,350 508,298 500,244 488,206', // 아시아(중동~러시아~중국~인도 벌지) '560,92 634,68 714,70 792,82 858,98 900,120 872,140 852,150 872,178 830,198 800,178 810,214 776,234 748,208 720,180 700,206 686,182 656,168 628,146 600,150 580,128 566,110', // 일본 '878,136 896,150 886,172 872,158', // 인도네시아/동남아 도서 '756,234 806,230 830,248 800,264 760,258', // 호주 '800,316 858,306 928,332 916,362 856,378 812,362 796,336' ]; // 위/경도 격자선 const LNG_LINES = [-150, -120, -90, -60, -30, 0, 30, 60, 90, 120, 150]; const LAT_LINES = [-60, -30, 0, 30, 60]; type Props = { rows: WorldIndexRow[]; }; type Pin = { row: WorldIndexRow; label: string; x: number; y: number; dir: 'up' | 'down' | 'flat'; radius: number; }; export default function WorldMarketMap({ rows }: Props) { const [active, setActive] = useState(null); // 좌표를 아는 국가만 핀으로. 등락 크기에 따라 반경 가변(6~10). const pins = useMemo(() => { return rows .map((row) => { const geo = EXCHANGE_GEO[row.countryCode]; if (!geo) { return null; } const { x, y } = project(geo.lat, geo.lng); const radius = Math.min(10, 6 + Math.abs(row.flucRateBp) / 80); return { row, label: geo.label, x, y, dir: moveDir(row.flucRateBp), radius }; }) .filter((c): c is Pin => c !== null); }, [rows]); // 기준일 = 핀 중 가장 최근 거래일 const asOf = useMemo(() => { return rows.reduce((max, c) => (max === null || c.tradeDate > max ? c.tradeDate : max), null); }, [rows]); const activePin = pins.find((c) => c.row.countryCode === active) ?? null; return (

세계 증시