feat(network-map): add jitter-based label placement and clamp function for improved edge label positioning
Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 2m20s

This commit is contained in:
2026-02-24 23:39:52 +07:00
parent d657b03abc
commit b76cbc15cb
+43 -15
View File
@@ -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 (
<g
key={e.edgeKey || `${e.from}-${e.to}-${idx}`}
@@ -521,12 +548,13 @@ export default function NetworkMapUnifi({
stroke={edgeColor}
strokeWidth={strokeWidth}
strokeLinecap="round"
opacity={dimFactor}
opacity={baseOpacity * dimFactor}
filter="url(#edgeGlow)"
/>
{(() => {
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 && (