// drawing.js — canvas drawing module const Drawing = (() => { let canvas, ctx; let isDrawing = false; let points = []; // [{x, y}, ...] let cities = []; // [{name, x, y}, ...] (% coords) const STROKE_COLOR = "#1a7fc4"; const STROKE_WIDTH = 2.5; function init(canvasEl) { canvas = canvasEl; ctx = canvas.getContext("2d"); // Pointer events (mouse + touch) canvas.addEventListener("pointerdown", onDown); canvas.addEventListener("pointermove", onMove); canvas.addEventListener("pointerup", onUp); canvas.addEventListener("pointerleave", onUp); canvas.style.touchAction = "none"; _resize(); window.addEventListener("resize", _resize); } function _resize() { if (!canvas) return; const { width, height } = canvas.getBoundingClientRect(); canvas.width = width * window.devicePixelRatio; canvas.height = height * window.devicePixelRatio; ctx.scale(window.devicePixelRatio, window.devicePixelRatio); _redraw(); } function _pos(e) { const r = canvas.getBoundingClientRect(); return { x: (e.clientX - r.left), y: (e.clientY - r.top), }; } function onDown(e) { e.preventDefault(); isDrawing = true; const p = _pos(e); points.push(p); ctx.beginPath(); ctx.moveTo(p.x, p.y); } function onMove(e) { if (!isDrawing) return; e.preventDefault(); const p = _pos(e); points.push(p); ctx.lineTo(p.x, p.y); ctx.strokeStyle = STROKE_COLOR; ctx.lineWidth = STROKE_WIDTH; ctx.lineJoin = "round"; ctx.lineCap = "round"; ctx.stroke(); } function onUp(e) { if (!isDrawing) return; isDrawing = false; e.preventDefault(); } function clear() { points = []; if (!ctx) return; const w = canvas.getBoundingClientRect().width; const h = canvas.getBoundingClientRect().height; ctx.clearRect(0, 0, w, h); _drawCities(); } function setCities(cityList) { cities = cityList || []; _drawCities(); } function _drawCities() { if (!ctx || !cities.length) return; const w = canvas.getBoundingClientRect().width; const h = canvas.getBoundingClientRect().height; cities.forEach(city => { const cx = (city.x / 100) * w; const cy = (city.y / 100) * h; // Dot ctx.beginPath(); ctx.arc(cx, cy, 5, 0, Math.PI * 2); ctx.fillStyle = "rgba(240,180,40,0.9)"; ctx.fill(); ctx.strokeStyle = "rgba(255,255,255,0.9)"; ctx.lineWidth = 1.5; ctx.stroke(); // Label ctx.font = "bold 11px 'DM Sans', sans-serif"; ctx.fillStyle = "#0b1f2a"; ctx.textAlign = "center"; // White pill background const textW = ctx.measureText(city.name).width + 10; const textH = 16; const tx = cx; const ty = cy - 14; ctx.save(); ctx.fillStyle = "rgba(255,255,255,0.88)"; ctx.beginPath(); ctx.roundRect(tx - textW / 2, ty - textH / 2 - 1, textW, textH, 4); ctx.fill(); ctx.restore(); ctx.fillStyle = "#0b1f2a"; ctx.fillText(city.name, tx, ty + 4); }); } function _redraw() { _drawCities(); if (!points.length) return; ctx.beginPath(); ctx.strokeStyle = STROKE_COLOR; ctx.lineWidth = STROKE_WIDTH; ctx.lineJoin = "round"; ctx.lineCap = "round"; points.forEach((p, i) => i === 0 ? ctx.moveTo(p.x, p.y) : ctx.lineTo(p.x, p.y)); ctx.stroke(); } function getPoints() { return [...points]; } function destroy() { window.removeEventListener("resize", _resize); } return { init, clear, setCities, getPoints, destroy }; })();