From b76cbc15cbf721fa3c37edb7c24009a053ef6ef4 Mon Sep 17 00:00:00 2001 From: shats Date: Tue, 24 Feb 2026 23:39:52 +0700 Subject: [PATCH] feat(network-map): add jitter-based label placement and clamp function for improved edge label positioning --- frontend/src/NetworkMapUnifi.jsx | 58 +++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/frontend/src/NetworkMapUnifi.jsx b/frontend/src/NetworkMapUnifi.jsx index e659ecb..a4c5ba2 100644 --- a/frontend/src/NetworkMapUnifi.jsx +++ b/frontend/src/NetworkMapUnifi.jsx @@ -119,6 +119,20 @@ function anchorPoint(pos, side, slotOffset = 0) { return { x: pos.x + slotOffset, y: pos.y - NODE_HEIGHT / 2 }; } +function clamp(val, min, max) { + return Math.max(min, Math.min(max, val)); +} + +function hash01(str) { + let h = 2166136261; + const s = String(str || ''); + for (let i = 0; i < s.length; i += 1) { + h ^= s.charCodeAt(i); + h = Math.imul(h, 16777619); + } + return (h >>> 0) / 4294967295; +} + export default function NetworkMapUnifi({ servers = [], connections = [], @@ -442,15 +456,24 @@ export default function NetworkMapUnifi({ return `M ${sx} ${sy} L ${sOutX} ${sOutY} Q ${cpx} ${cpy} ${eOutX} ${eOutY} L ${ex} ${ey}`; }, [computeCurvePoints]); - /** Точка для подписи: середина ребра + смещение по перпендикуляру (чтобы подпись была у ребра, а не уезжала) */ + /** Точка подписи на кривой с небольшим jitter и ограничением в пределах карты */ const labelPlacement = useCallback((e, level = 0) => { - const { mx, my, perpX, perpY } = computeCurvePoints(e); - const baseOffset = 18; - const stepOffset = 26; - const x = mx + perpX * (baseOffset + level * stepOffset); - const y = my + perpY * (baseOffset + level * stepOffset); - return { x, y }; - }, [computeCurvePoints]); + const geom = computeCurvePoints(e); + const jitter = (hash01(e.edgeKey) - 0.5) * 0.24; + const t = clamp(0.42 + jitter, 0.26, 0.74); + const omt = 1 - t; + const xCurve = omt * omt * geom.sOutX + 2 * omt * t * geom.cpx + t * t * geom.eOutX; + const yCurve = omt * omt * geom.sOutY + 2 * omt * t * geom.cpy + t * t * geom.eOutY; + const baseOffset = 16; + const stepOffset = 18; + const x = xCurve + geom.perpX * (baseOffset + level * stepOffset); + const y = yCurve + geom.perpY * (baseOffset + level * stepOffset); + const margin = 22; + return { + x: clamp(x, margin, Math.max(margin, size.w - margin)), + y: clamp(y, margin, Math.max(margin, size.h - margin)), + }; + }, [computeCurvePoints, size.w, size.h]); if (servers.length === 0) return null; @@ -500,11 +523,15 @@ export default function NetworkMapUnifi({ const used = []; const LABEL_W = 118; const LABEL_H = 22; + const denseMode = routedEdges.length > 14; return routedEdges.map((e, idx) => { const dimFactor = - hoveredEdgeKey && hoveredEdgeKey !== e.edgeKey ? 0.25 : 1; - const strokeWidth = hoveredEdgeKey === e.edgeKey ? 3 : 2; + hoveredEdgeKey && hoveredEdgeKey !== e.edgeKey ? 0.16 : 1; + const strokeWidth = hoveredEdgeKey === e.edgeKey ? 3 : 1.8; const edgeColor = getTunnelColor(e.tunnelType); + const isFocused = + hoveredEdgeKey === e.edgeKey || selectedEdge?.edgeKey === e.edgeKey; + const baseOpacity = denseMode ? 0.62 : 0.82; return ( {(() => { - if (!e.edgeLabel) return null; - const minDist = 70; + const showLabel = e.edgeLabel && (!denseMode || isFocused || e.stale); + if (!showLabel) return null; + const minDist = denseMode ? 56 : 70; let level = 0; let x; let y; while (level < 10) { @@ -557,9 +585,9 @@ export default function NetworkMapUnifi({ width={w} height={h} rx={7} - fill="rgba(2,6,23,0.9)" + fill={isFocused ? 'rgba(2,6,23,0.94)' : 'rgba(2,6,23,0.85)'} stroke={isStale ? staleColor : edgeColor} - strokeWidth={1} + strokeWidth={isFocused ? 1.3 : 1} strokeDasharray={isStale ? '2,2' : 'none'} /> {isStale && (