diff --git a/public/js/app.js b/public/js/app.js index 135e2bc..5c04bfe 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -869,9 +869,13 @@ const geoData = servers .filter(s => s.lat && s.lng) - .map(s => ({ + .map((s, idx) => ({ name: s.job, - value: [shiftLng(s.lng), s.lat], + // Add a tiny, stable jitter to marker positions to prevent perfect overlap of nodes in the same city + value: [ + shiftLng(s.lng) + (Math.sin(idx * 12.9898) * 0.008), + s.lat + (Math.cos(idx * 78.2330) * 0.008) + ], job: s.job, city: s.city, country: s.country, @@ -957,7 +961,6 @@ const end = getShiftedCoords(route.dest); if (start && end) { // Canonical points for grouping (independent of direction) - // Sort by longitude then latitude const pts = [start, end].slice().sort((a, b) => a[0] - b[0] || a[1] - b[1]); const key = `${pts[0][0].toFixed(4)},${pts[0][1].toFixed(4)}_${pts[1][0].toFixed(4)},${pts[1][1].toFixed(4)}`; if (!routeGroups[key]) routeGroups[key] = []; @@ -969,24 +972,32 @@ const routes = routeGroups[key]; const count = routes.length; + // Spread overlapping lines visually routes.forEach((item, i) => { const { route, start, end } = item; // Identify if the route is in the "canonical" direction const pts = [start, end].slice().sort((a, b) => a[0] - b[0] || a[1] - b[1]); - const isForward = (start === pts[0]); + const isForward = (start[0] === pts[0][0] && start[1] === pts[0][1]); - // Calculate curveness: ensure all lines are slightly curved + // Use a deterministic hash of the path to jitter the curves to avoid coincidence with other nearby paths + const pathSeed = pts[0][0] + pts[0][1] * 2 + pts[1][0] * 3 + pts[1][1] * 4; + const curveJitter = (Math.abs(Math.sin(pathSeed)) * 0.12) - 0.06; // Variation of +/- 0.06 + + // Calculate curveness: ensure all lines are curved and distinct let finalCurve = 0; if (count === 1) { - finalCurve = 0.15; // Default slight curve for single lines + // Default curve for single lines with a bit of path-specific variation + finalCurve = 0.2 + curveJitter; } else { - // Spread overlapping lines: 0.15, -0.15, 0.3, -0.3... - // This creates an "eye" or "fan" effect where no line is straight - const magnitude = 0.15 + Math.floor(i / 2) * 0.15; + // Spread overlapping lines: 0.2, -0.2, 0.35, -0.35... + // Use a dynamic step to keep lines within a reasonable arc range + const step = Math.min(0.2, 0.5 / Math.ceil(count / 2)); + const magnitude = 0.2 + Math.floor(i / 2) * step; const spread = (i % 2 === 0) ? magnitude : -magnitude; - // Adjust sign based on direction so they occupy unique visual slots - finalCurve = isForward ? spread : -spread; + + // Adjust based on direction so A->B and B->A land in different visual arcs (forming an "eye") + finalCurve = (isForward ? spread : -spread) + curveJitter; } finalSeries.push({ @@ -1038,6 +1049,7 @@ const regions = new Set(geoData.map(d => d.country)).size; dom.globeTotalRegions.textContent = regions; } + } // ---- Update Dashboard ----